[{"content":"","date":"1 août 2026","externalUrl":null,"permalink":"/tags/google-photos/","section":"Tags","summary":"","title":"Google-Photos","type":"tags"},{"content":"If you\u0026rsquo;re reading this, you\u0026rsquo;re probably in the same boat I was: a happy Proton subscriber who wants to leave Google Photos behind, but can\u0026rsquo;t find a straightforward migration path, especially if you\u0026rsquo;re not on Windows.\nThe official Proton desktop apps handle photo upload natively on Windows, but on macOS and Linux you\u0026rsquo;re left with the proton-drive CLI and documentation that doesn\u0026rsquo;t tell you half of what you need to know. After spending days going down rabbit holes, I built (in the end it was not me, but Claude :P) gphoto2proton, and learned some hard lessons about Proton\u0026rsquo;s architecture along the way.\nThe Pain Point # Google Takeout gives you your photos as a set of multi-gigabyte .tgz archives. Each archive contains your media files alongside JSON sidecar files with metadata (capture date, GPS location, description) and album.json files describing your album structure.\nIf you\u0026rsquo;re on Windows, the Proton Drive desktop app just works: it uploads to both Drive and Photos. But on other platforms, you\u0026rsquo;re missing features. Album creation, in particular, has no official API or tooling. The proton-drive CLI (v0.7.0, released July 31, 2026) has photo upload and album create subcommands in its source code, but neither the CLI\u0026rsquo;s README nor Proton\u0026rsquo;s official support page mentions them. They show up in proton-drive --help, but you\u0026rsquo;d have to know to look.\nThe Architecture Problem: Drive ≠ Photos # Here\u0026rsquo;s the first thing nobody tells you: Proton Drive and Proton Photos are not the same thing.\nThey share a backend (your files end up in the same encrypted storage. And if I trust what I can see with the Drive API, even the encrypted storage is different for the 2 services), but they\u0026rsquo;re accessed through completely different APIs:\nProton Drive uses the standard Drive API: files go to \u0026ldquo;My Files\u0026rdquo;, manageable through the SDK, the CLI\u0026rsquo;s filesystem commands, or third-party bridges like rclone.\nProton Photos has a separate API at photos-api.proton.me (undocumented): the photo timeline and albums live in a protected volume that regular Drive API calls can\u0026rsquo;t touch.\nThis distinction matters because uploading a photo to your Drive folder does not make it appear in your Photos timeline. Two completely different operations.\nApproach 1: The Go Binary - Cross-Platform but Compromised # My first attempt was a Go CLI binary that could run on macOS, Linux, and Windows. It used the rclone/Proton-API-Bridge library: a third-party SDK that wraps Proton\u0026rsquo;s Drive API with the necessary encryption and authentication.\nThe approach was elegant on paper:\nStreaming reader: read .tgz archives directly without extracting to disk (saving 80GB+ of temporary space per archive)\nEXIF restoration: pipe each file through exiftool to embed the original photoTakenTime.timestamp from Google\u0026rsquo;s JSON sidecar\nUpload to Drive: via the Proton-API-Bridge SDK, files land in a gphoto2proton folder under My Files\nAlbum creation: via direct HTTP calls to the undocumented photos-api.proton.me/photos/v1/albums endpoint\nThe Go binary works well and is still the best option if you need cross-platform support. But it has a fundamental limitation: photos end up in your Drive folder, not in the Photos timeline. You can see them in the Photos web app (Proton does scan Drive for photos), but they don\u0026rsquo;t get the full timeline treatment, correct dates, album association, etc.\nThe album creation via the undocumented Photos API is also fragile. It\u0026rsquo;s reverse-engineered from web traffic, there\u0026rsquo;s no contract or changelog, and it could break at any Proton update.\nApproach 2: The Bash Script - Do It Right # After wrestling with the Go approach, I discovered that the official proton-drive CLI (from ProtonDriveApps/sdk) has undocumented photo and album subcommands that talk directly to the Photos API through Proton\u0026rsquo;s own code.\nThis led to a second, completely different approach: a bash script that wraps the CLI and handles the full pipeline:\nExtract: tar xzf one archive at a time\nApply capture dates: for videos, the CLI falls back to filesystem mtime, so the script reads photoTakenTime.timestamp from the JSON sidecar and sets it via touch -t\nUpload: proton-drive photo upload -c skip uploads directly to the Photos timeline (deduplicating by content hash)\nVerify: re-run upload (should transfer 0) + check every SHA1 is in the timeline\nAlbums: proton-drive album create + album add-photo recreates albums with photos in batches of 200\nValidate: confirm every expected photo exists in each album on the server\nCleanup: remove extracted files, mark archive done\nThis approach puts photos exactly where they should be: in the Photos timeline with working albums. The downsides:\nLinux only - the script uses flock, GNU stat, and assumes pass for credentials\nDisk-heavy - extracts each 50GB archive to ~80GB on disk before uploading\nSlower - no streaming; extract-wait-upload-wait-cleanup cycle per archive\nWhy Two Approaches? # Because Proton\u0026rsquo;s architecture forced my hand. The Go binary is the right tool if:\nYou\u0026rsquo;re on macOS or Windows\nYou want streaming (no disk extraction)\nYou\u0026rsquo;re OK with photos in Drive (not the Photos timeline)\nThe bash script wins if:\nYou\u0026rsquo;re on Linux (or can spin up a Linux box)\nYou want photos in the Photos timeline with working albums\nYou have enough disk space for temporary extraction\nI use both: the Go binary for a quick cross-platform option, the bash script for the \u0026ldquo;proper\u0026rdquo; import to Photos.\nThe Tool # The project is gphoto2proton, open-source (MIT). It includes:\nA Go CLI binary with streaming archive reading, EXIF restoration, SQLite-based resume safety, and 126 passing tests\nA bash script for full Photos timeline import via the proton-drive CLI\nFull documentation at gphoto2proton.mornati.net\nHomebrew formula for easy macOS/Linux install (brew install gphoto2proton)\nThe migration is not trivial — ~354GB across 9 archives — but the result is worth it: all photos in Proton Photos with albums intact, dates correct, and no Google account needed.\nScript execution sample # If you follow the documentation, you will be ready to go in few minutes. I put here the bash script output to let you see what it allows for you. I think this saves me days (weeks?) in manual operations.\nTAKEOUT_DIR=/media/12tb/photos ~/gphoto2proton/gphoto2proton-import.sh [19:26:09] gphoto2proton-import: takeout=/media/12tb/photos work=/home/mmornati/gphoto2proton/work logs=/home/mmornati/gphoto2proton/logs state=/home/mmornati/gphoto2proton/state [19:26:09] CLI=proton-drive credentials_store=pass [19:26:10] authentication OK (store: pass) [19:26:10] disk space OK: avail=309352MB, need~=104448MB [19:26:10] skipping takeout-20260729T191209Z-001.tgz (already done) [19:26:10] [19:26:10] ==== takeout-20260729T191210Z-1-001.tgz (1/8) ==== [19:26:10] extraction exists, resuming ... [19:26:10] stripping macOS metadata junk (._*, .DS_Store) ... [19:26:10] applying original capture dates from sidecar JSON ... [19:28:35] applied capture dates from sidecar JSON to 16562 files [19:28:35] building manifest (sha1sum of all media files) ... [19:28:39] sha1sum progress: 500 files hashed total 26956 drwxrwxr-x 6 mmornati mmornati 4096 août 1 09:26 ./ [19:28:39] sha1sum progress: 500 files hashed [19:28:44] sha1sum progress: 1000 files hashed [19:28:49] sha1sum progress: 1500 files hashed [19:28:54] sha1sum progress: 2000 files hashed [19:28:57] sha1sum progress: 2500 files hashed [19:29:02] sha1sum progress: 3000 files hashed [19:29:08] sha1sum progress: 3500 files hashed [19:29:13] sha1sum progress: 4000 files hashed [19:29:18] sha1sum progress: 4500 files hashed [19:29:32] sha1sum progress: 5000 files hashed [19:29:36] sha1sum progress: 5500 files hashed [19:29:41] sha1sum progress: 6000 files hashed [19:29:46] sha1sum progress: 6500 files hashed [19:29:52] sha1sum progress: 7000 files hashed [19:29:57] sha1sum progress: 7500 files hashed [19:30:02] sha1sum progress: 8000 files hashed [19:30:07] sha1sum progress: 8500 files hashed [19:30:17] sha1sum progress: 9000 files hashed [19:30:22] sha1sum progress: 9500 files hashed [19:30:28] sha1sum progress: 10000 files hashed [19:30:32] sha1sum progress: 10500 files hashed [19:30:39] sha1sum progress: 11000 files hashed [19:30:44] sha1sum progress: 11500 files hashed [19:30:50] sha1sum progress: 12000 files hashed [19:30:57] sha1sum progress: 12500 files hashed [19:31:03] sha1sum progress: 13000 files hashed [19:31:08] sha1sum progress: 13500 files hashed [19:31:13] sha1sum progress: 14000 files hashed [19:31:18] sha1sum progress: 14500 files hashed [19:31:23] sha1sum progress: 15000 files hashed [19:31:29] sha1sum progress: 15500 files hashed [19:31:34] sha1sum progress: 16000 files hashed [19:31:40] sha1sum progress: 16500 files hashed [19:31:45] sha1sum progress: 17000 files hashed [19:31:47] sha1sum complete: 17236 files [19:31:47] expected media: 17236 files (17197 unique) [19:31:47] uploading (conflict strategy: skip) ... Lessons Learned # Proton\u0026rsquo;s API surface is fragmented: Drive and Photos are different systems with different APIs. Don\u0026rsquo;t assume uploading to one gets you the other.\nThe CLI has undocumented features: the proton-drive CLI\u0026rsquo;s README and Proton\u0026rsquo;s official support page only document filesystem commands, but proton-drive --help reveals full photo upload, photo timeline, album create, and album add-photo support. They just aren\u0026rsquo;t documented in the written docs yet.\nWindows has the best migration story: Proton\u0026rsquo;s official Windows app handles everything from backup to album creation. On other platforms, you need custom tooling.\nUndocumented APIs are a trap: my first approach relied on reverse-engineering photos-api.proton.me, which works today but has no stability guarantee. The CLI approach is more future-proof since it\u0026rsquo;s Proton\u0026rsquo;s own code.\nThe code is at github.com/mmornati/gphoto2proton. If you\u0026rsquo;ve been sitting on a Google Takeout export wondering how to get it into Proton, hopefully this saves you some time.\n","date":"1 août 2026","externalUrl":null,"permalink":"/how-i-built-gphoto2proton-to-migrate-354gb-of-google-photos-to-proton/","section":"Posts","summary":"","title":"How I built gphoto2proton to migrate 354GB of Google Photos to Proton ","type":"posts"},{"content":"","date":"1 août 2026","externalUrl":null,"permalink":"/tags/migration/","section":"Tags","summary":"","title":"Migration","type":"tags"},{"content":"","date":"1 août 2026","externalUrl":null,"permalink":"/tags/open-source/","section":"Tags","summary":"","title":"Open-Source","type":"tags"},{"content":"","date":"1 août 2026","externalUrl":null,"permalink":"/tags/proton/","section":"Tags","summary":"","title":"Proton","type":"tags"},{"content":"","date":"1 août 2026","externalUrl":null,"permalink":"/tags/self-hosted/","section":"Tags","summary":"","title":"Self-Hosted","type":"tags"},{"content":"","date":"26 juillet 2026","externalUrl":null,"permalink":"/tags/email-alias-management/","section":"Tags","summary":"","title":"Email-Alias-Management","type":"tags"},{"content":" Introduction # In recent months/years, two French organizations suffered significant data breaches: Cultura (September 2024) and the Fédération Française de Tennis (January 2026). Months later, the leaked data is actively being used in targeted phishing campaigns.\nThis post traces the full chain — from the initial breach to the phishing email landing in an inbox — using real email headers as evidence. But more importantly, it\u0026rsquo;s a training exercise: it shows how a simple practice — one email address per website — transforms you from a passive victim into someone who can instantly identify the source of a leak, contain the damage in one click, and move on.\nLet\u0026rsquo;s see how.\nThe Two Breaches # Cultura (September 2024) # Detail Info Date September 2024 Attack vector Compromised external IT service provider (Octave) Records exposed ~1.5M unique email addresses Data types Names, email addresses, phone numbers, physical addresses, order history HIBP added September 25, 2025 Source Have I Been Pwned, The Record Cultura is a major French retailer of cultural products (books, music, games, instruments). The breach was attributed to an attack on Octave, their IT service provider. The data was later posted on BreachForums by a user named TanaDeMerde, exposing over 2.1 million lines of customer data.\nFFT — French Tennis Federation (January 2026) # Detail Info Date January 12, 2026 Attack vector Cyberattack on a platform used by affiliated clubs Records exposed ~1.2M licensees Data types Names, email addresses, phone numbers, postal addresses, license numbers Official statement FFT communiqué Coverage Le Figaro, 01net, Sud-Ouest The FFT is France\u0026rsquo;s second-largest sports federation. The attackers gained access to a club management platform, exfiltrating personal data of over a million licensees.\nThe Attack Chain # Data breaches (Cultura / FFT) ↓ Email lists sold or published on dark web ↓ Phishing operator acquires the data, including aliases ↓ Operator uses compromised SendGrid accounts to send emails via API ↓ SendGrid processes the emails through geopod-ismtpd infrastructure ↓ SimpleLogin receives the email and forwards it to the real mailbox ↓ Phishing email lands in the user\u0026#39;s inbox (or spam) Both phishing campaigns observed follow the same pattern:\nSpoofed sender identity: Fake health insurance / payment reminder services\nSubject lines in French: Targeting French users specifically\nSendGrid infrastructure: Emails pass SPF, DKIM, and DMARC because they originate from legitimate SendGrid servers\nSendGrid API sent: The X-SG-* headers and geopod-ismtpd hostname confirm API submission, not SMTP\nThe Alias as a Sensor: One Address Per Website Changes Everything # Before diving into the headers, let\u0026rsquo;s establish the core concept that made this analysis possible.\nThe Principle # Instead of giving every website your real email address, you give each one a unique alias:\ncultura@yourdomain.simplelogin.com → for Cultura fft@yourdomain.simplelogin.com → for FFT newsletter@yourdomain.simplelogin.com → for newsletters bank@yourdomain.simplelogin.com → for your bank Every alias forwards to the same real mailbox. To the outside world, each alias looks like a different email address. To you, they all arrive in one inbox.\nWhy This is a Superpower # The moment spam or a phishing email arrives on one of these aliases, you know exactly what happened:\nYou receive spam on → You immediately know → cultura@yourdomain.simplelogin.com Cultura\u0026rsquo;s data was leaked or sold fft@yourdomain.simplelogin.com FFT\u0026rsquo;s data was leaked or sold bank@yourdomain.simplelogin.com Your bank has a problem (or their partner sold data) No guesswork. No \u0026ldquo;did I use my Gmail or Outlook here?\u0026rdquo; No hunting through password managers to check which email you used on which site.\nThe Disposal Lifecycle # When an alias is compromised, the fix is trivial:\n1. DELETE the compromised alias in SimpleLogin (or disable it) 2. CREATE a new alias for that service (e.g. cultura2@...) 3. UPDATE the account with the new alias 4. DONE — any future email to the old alias is silently dropped That\u0026rsquo;s it. You don\u0026rsquo;t change your real email address. You don\u0026rsquo;t update 50 other accounts. You don\u0026rsquo;t worry about the old alias being used for password resets or impersonation.\nOnce deleted, SimpleLogin does not forward any emails sent to that alias. They are discarded at the server level. The phisher can keep sending — the emails vanish into a black hole.\nWithout Aliases # If you used your real email everywhere and one service leaks it:\n1. Your email is now in the hands of spammers, phishers, and data brokers 2. You cannot \u0026#34;un-leak\u0026#34; it 3. Every phishing email that arrives looks legitimate (it\u0026#39;s your real address) 4. You cannot tell which service leaked it 5. Your only option is to abandon the address and notify everyone you know This is the old world. Aliases are the new world.\nNow let\u0026rsquo;s see how all of this played out in practice with the Cultura and FFT leaks.\nEmail Header Analysis # Below is a real email header captured from one of these phishing emails. Personal details have been replaced with sample data.\nRaw Headers (Anonymized) # Return-Path: \u0026lt;sl.lmycyibrgq2tqmrwgyztonrmeazdgnrxgy2tsxi.XXXX@simplelogin.co\u0026gt; X-Original-To: user@protonmail.com Delivered-To: user@protonmail.com Received: from mail-200161.simplelogin.co (mail-200161.simplelogin.co [176.119.200.161]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) by mailin.protonmail.ch (Postfix) with ESMTPS id XXXXX for \u0026lt;user@protonmail.com\u0026gt;; Fri, 03 Jul 2026 04:59:35 +0000 (UTC) Authentication-Results: mail.protonmail.ch; dmarc=pass (p=quarantine dis=none) header.from=simplelogin.co Authentication-Results: mail.protonmail.ch; spf=pass smtp.mailfrom=simplelogin.co Authentication-Results: mail.protonmail.ch; dkim=pass (1024-bit key) header.d=simplelogin.co header.i=@simplelogin.co Arc-Seal: i=1; a=rsa-sha256; d=simplelogin.co; s=arc-20230626; ... Arc-Message-Signature: i=1; a=rsa-sha256; d=simplelogin.co; s=arc-20230626; ... Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=simplelogin.co; s=dkim; ... Date: Fri, 03 Jul 2026 04:59:32 +0000 Message-Id: \u0026lt;XXXXXXXXXX@geopod-ismtpd-15\u0026gt; Subject: Rappel de votre versement X-Simplelogin-Type: Forward X-Simplelogin-Emaillog-Id: XXXXXXXXXX X-Simplelogin-Envelope-To: shopping@user.simplelogin.com From: \u0026#34;Spoofed Company - noreply at fake-domain.com\u0026#34; \u0026lt;noreply_at_fake-domain_com_random@simplelogin.co\u0026gt; To: shopping@user.simplelogin.com List-Unsubscribe: \u0026lt;mailto:unsubscribe@simplelogin.co?subject=un.XXX\u0026gt; Key Findings from the Headers # 1. SimpleLogin\u0026rsquo;s Alias Protection # The email was sent to shopping@user.simplelogin.com — an alias created for Cultura. SimpleLogin forwarded it to the real mailbox user@protonmail.com. The original sender never sees the real address.\nHowever, because the alias was used on Cultura\u0026rsquo;s website and Cultura was breached, the alias itself ended up in the leaked dataset. The phisher now knows exactly which alias to target.\n2. The Original Sender is Encoded, Not Hidden # SimpleLogin rewrites the From: header, but the original claimed sender leaks through:\nFrom: \u0026#34;Spoofed Company - noreply at fake-domain.com\u0026#34; \u0026lt;noreply_at_fake-domain_com_random@simplelogin.co\u0026gt; The display name preserves the original claimed identity. The email local part uses the convention: originaluser_at_originaldomain_random@simplelogin.co. By reversing the _at_ substitution, you can extract the claimed sender: noreply@fake-domain.com.\nThis is a phishing indicator, not a real trace — the domain is spoofed.\n3. The Sending Infrastructure: SendGrid\u0026rsquo;s geopod-ismtpd # The Message-Id domain is geopod-ismtpd-15. This is a well-documented SendGrid internal hostname.\nFortra (October 2024) documented this exact indicator:\n\u0026ldquo;The received field also commonly contains \u0026lsquo;geopod-ismtpd,\u0026rsquo; which has been classified by SendGrid abuse reports as being associated with phishing, spam, and spoofing.\u0026rdquo;\nThe raw Received header from SendGrid would show by geopod-ismtpd-15 (SG) with ESMTP id, but SimpleLogin strips the intermediate SendGrid Received headers during forwarding. Only the Message-Id suffix remains as evidence.\nKaseya/INKY (January 2026) confirmed in their analysis of a separate SendGrid phishing campaign:\n\u0026ldquo;One hop reads: \u0026lsquo;Received from \u0026hellip; by geopod-ismtpd-15 (SG) with HTTP id \u0026hellip;\u0026rsquo;, indicating that SendGrid\u0026rsquo;s geopod-ismtpd servers generated the message. The presence of \u0026lsquo;geopod-ismtpd\u0026rsquo; and \u0026lsquo;(unknown)\u0026rsquo; in Received headers is a common indicator that the email originated from SendGrid.\u0026rdquo;\n4. SPF/DKIM/DMARC All Pass — Because It\u0026rsquo;s Legitimate Infrastructure # The email passes all three authentication checks because it was sent through SendGrid\u0026rsquo;s authorized servers. A receiving mail server sees:\nspf=pass — SendGrid\u0026rsquo;s IPs are authorized by simplelogin.co\u0026rsquo;s SPF record\ndkim=pass — SimpleLogin\u0026rsquo;s DKIM signature is valid\ndmarc=pass — Alignment is maintained\nTo the receiving server, this looks like a legitimate email from SimpleLogin, not a phishing attempt. The authentication layer cannot distinguish between a legitimate forwarded email and a phishing email sent through a compromised sender\u0026rsquo;s account upstream.\nThe Two Phishing Emails Received # Email 1: Cultura Alias Targeted # Field Value Alias used shopping@user.simplelogin.com Claimed sender noreply@idx.inc Subject Rappel de votre versement Theme Fake payment reminder / health insurance refund Message-ID geopod-ismtpd-15 Date July 3, 2026 Email 2: FFT Alias Targeted # Field Value Alias used sports@user.simplelogin.com Claimed sender noreply@appsys.co.uk Subject Rappel: Mettez à jour votre dossier assurance Theme Fake health insurance document update Message-ID geopod-ismtpd-1 Date July 1, 2026 Both emails share identical patterns:\nClaim to be from a French health insurance / payment service\nUrge the recipient to take action (update dossier, confirm payment)\nUse French language exclusively\nSent via SendGrid infrastructure\nArrived within days of each other, suggesting the same operator running both lists\nThe Disposal Lifecycle in Practice # Let\u0026rsquo;s walk through exactly what happens when you delete a compromised alias.\nBefore Deletion # The alias exists in SimpleLogin\u0026rsquo;s system. Any email sent to it is forwarded to your real mailbox:\nphishing@fake.com → shopping@user.simplelogin.com → user@protonmail.com ✓ After Deletion # The alias no longer exists. SimpleLogin receives the email and drops it immediately. No bounce, no forward, no notification to the sender:\nphishing@fake.com → (alias does not exist) → ✗ discarded silently The phisher has no way to know the alias is gone. Their emails keep being sent into the void.\nWhat About the Account on the Leaked Website? # You can log into the breached service and update your email to a fresh alias. The attacker cannot follow you because they only have the old alias.\nThis is the key insight that most people miss: your relationship with a service is tied to an alias, not to your real identity. When that alias is burned, you replace it. The service keeps working. The phisher loses access to you.\nReal Example: What I Did # When I received the phishing emails:\nI checked the X-Simplelogin-Envelope-To header to confirm which alias was targeted\nI logged into SimpleLogin and deleted both the Cultura and FFT aliases\nI created fresh aliases for both services\nI updated my accounts with the new aliases\nTotal time: about 2 minutes\nThe phisher can keep my old aliases forever. They\u0026rsquo;re useless now.\nHow to Protect Yourself: The Alias Mindset # This section is the core of the article. If you remember only one thing, remember this process.\nThe Golden Rule # One alias per website. Never reuse. Never share.\nIf you follow this rule, you turn every alias into a sensor. You will always know which service leaked your data. You can always cut the link with one click.\nThe Lifecycle (Memorize This) # Create → Use on one site → Breach happens → Spam arrives ↓ Identify source (alias name) ↓ Delete alias (silent drop) ↓ Create fresh alias ↓ Update the account ↓ Done. The attacker is locked out. Checklist for Everyone # 1. Start Using Aliases Today # Sign up for SimpleLogin (free tier: 15 aliases)\nOr use addy.io, Firefox Relay, \u0026hellip; (all offer alias features)\nCreate a naming convention: servicename@yourdomain.simplelogin.com\nUse the browser extension for one-click alias creation\n2. When You Receive Unexpected Email # Do not click anything — not even \u0026ldquo;Unsubscribe\u0026rdquo;\nCheck the alias it was sent to (in SimpleLogin, look at X-Simplelogin-Envelope-To)\nIdentify the source — which website did you use that alias on?\nDelete the alias in SimpleLogin immediately\nReport the phishing to abuse@sendgrid.com if SendGrid infrastructure is involved\nCreate a new alias for the service you need to keep using\n3. If You Were Affected by These Breaches Specifically # Delete the aliases you used on Cultura and FFT\nCreate new aliases and update your account profiles on those sites\nMonitor SimpleLogin\u0026rsquo;s activity log for any other aliases receiving unexpected mail\nReport the phishing emails to SendGrid at abuse@sendgrid.com with full headers\n4. General Hygiene # Use a password manager and never reuse passwords (aliases protect your email, passwords protect your accounts — you need both)\nEnable PGP encryption on SimpleLogin so forwarded email content is encrypted\nCheck Have I Been Pwned regularly\nSet up SimpleLogin\u0026rsquo;s browser extension for one-click alias creation\nUse subdomains in SimpleLogin to keep aliases organized by category (shopping, finance, social, etc.)\nConclusion # The Cultura and FFT breaches demonstrate a complete lifecycle: data is stolen, sold on the dark web, acquired by phishing operators, and weaponized via legitimate email infrastructure like SendGrid. The use of compromised SendGrid accounts means these emails pass all authentication checks, making them difficult to block at the gateway level.\nBut the story doesn\u0026rsquo;t end with \u0026ldquo;they have my email.\u0026rdquo; Because of aliases, the story ends with: \u0026ldquo;I know exactly where this came from, I deleted the alias in 30 seconds, and the attacker is now sending emails into a black hole.\u0026rdquo;\nThis is the mindset shift that aliases enable:\nOld mindset Alias mindset \u0026ldquo;My email was leaked, I\u0026rsquo;m helpless\u0026rdquo; \u0026ldquo;My alias was leaked, I know the source\u0026rdquo; \u0026ldquo;I have to change my email everywhere\u0026rdquo; \u0026ldquo;I delete one alias, done\u0026rdquo; \u0026ldquo;I don\u0026rsquo;t know which site sold my data\u0026rdquo; \u0026ldquo;The alias name tells me immediately\u0026rdquo; \u0026ldquo;Spam keeps arriving forever\u0026rdquo; \u0026ldquo;The alias is gone, spam is dropped\u0026rdquo; Data breaches are inevitable. Companies will continue to be hacked. But you can design your digital identity so that a breach at one service is contained — it affects exactly one alias and nothing else.\nThe geopod-ismtpd hostname remains a reliable indicator of SendGrid abuse. If you see it in unexpected emails, report it. And if you haven\u0026rsquo;t started using email aliases yet, today is the day to begin.\nOne website. One alias. No exceptions.\nReferences # Cultura breach on Have I Been Pwned\nFFT official communiqué (January 12, 2026)\nFortra blog: Active Phishing Campaign — Twilio SendGrid Abuse (Oct 2024)\nKaseya/INKY: OpenAI invoice scam driven by SendGrid abuse (Jan 2026)\nAbuseIPDB reports for geopod-ismtpd\nSimpleLogin API documentation\nThe Record: France retailers hacked\n","date":"26 juillet 2026","externalUrl":null,"permalink":"/from-data-leak-to-phishing-campaign/","section":"Posts","summary":"","title":"From Data Leak to Phishing Campaign","type":"posts"},{"content":"","date":"26 juillet 2026","externalUrl":null,"permalink":"/tags/leak/","section":"Tags","summary":"","title":"Leak","type":"tags"},{"content":"","date":"26 juillet 2026","externalUrl":null,"permalink":"/tags/phishing/","section":"Tags","summary":"","title":"Phishing","type":"tags"},{"content":"","date":"26 juillet 2026","externalUrl":null,"permalink":"/tags/security/","section":"Tags","summary":"","title":"Security","type":"tags"},{"content":"","date":"26 juillet 2026","externalUrl":null,"permalink":"/tags/simplelogin/","section":"Tags","summary":"","title":"Simplelogin","type":"tags"},{"content":"","date":"28 juin 2026","externalUrl":null,"permalink":"/tags/ai-routing/","section":"Tags","summary":"","title":"Ai-Routing","type":"tags"},{"content":"","date":"28 juin 2026","externalUrl":null,"permalink":"/tags/mcp/","section":"Tags","summary":"","title":"Mcp","type":"tags"},{"content":"","date":"28 juin 2026","externalUrl":null,"permalink":"/tags/model-context-protocol/","section":"Tags","summary":"","title":"Model-Context-Protocol","type":"tags"},{"content":" 1. Introduction: The Age of Model Abundance # The AI assistant landscape in mid-2026 is one of abundance. According to McKinsey\u0026rsquo;s State of AI report, 78% of organizations now use AI regularly, and the number of available models has exploded. Developers today face a bewildering zoo of choices: GPT‑4.1, Claude 4 Sonnet, DeepSeek‑V3, Gemini 2.5 Pro, Llama 4, Mistral Large 2, and dozens more. Each comes with its own strengths, price tag, and latency profile. The natural reaction is \u0026ldquo;choice paralysis\u0026rdquo; – picking the right model for a given task has become a non‑trivial problem in itself.\nThe core problem is that no single model excels at everything. Using an expensive \u0026ldquo;super‑model\u0026rdquo; for every task—from formatting a docstring to debugging a memory leak—is wasteful and suboptimal. It drives up costs, increases latency, and often produces worse results than a purpose‑built specialist.\nThis post argues that intelligent delegation—model routing and orchestration—is the missing piece for cost‑effective, high‑quality AI development. Instead of forcing one model to handle all requests, we can build a lightweight \u0026ldquo;router\u0026rdquo; that dispatches each task to the model best suited for it. The result: lower costs, faster responses, and better quality. Let\u0026rsquo;s explore how.\n2. The Model Performance Reality # 2.1. No Universal Champion # Benchmarks reveal stark specialisation. On HumanEval (code generation), DeepSeek‑V3 and CodeLlama 70B outperform generalists like GPT‑4.1 and Claude 4 Sonnet by a significant margin. Yet on MMLU-Pro (knowledge and reasoning), the generalists lead, and on creative writing tasks, Claude 4 Sonnet consistently wins in LMSYS Chatbot Arena rankings (LMSYS, 2026). The data is clear: task fit matters more than raw parameter count. A 7B‑parameter model trained specifically on code can beat a 175B generalist on syntax formatting.\nSmaller, specialised models frequently outperform larger generalists in their niche. For example, on a task like \u0026ldquo;generate a JSON schema from a Python dataclass,\u0026rdquo; CodeLlama 7B often produces more accurate and compact output than GPT‑4.1, while costing a fraction of the compute.\n2.2. Cost Disparities # The financial gap is enormous. Top‑tier models like GPT‑4.1 and Claude 4 Opus charge around $12 per million input tokens; workhorse models like GPT‑4o mini or Claude 3.5 Haiku cost $0.15 per million—an 80× difference (OpenRouter, 2026). According to recent industry statistics from Artificial Analysis, Anthropic\u0026rsquo;s Claude models (Claude 4 Sonnet and Claude 4 Opus) are the most widely adopted in enterprise production environments, followed by OpenAI\u0026rsquo;s GPT‑4.1 series. Simple tasks such as linting, boilerplate generation, or documentation formatting do not need frontier intelligence. Paying for a flagship model to auto‑complete a docstring is like using a Ferrari to pick up groceries.\nDelegating the right task to the right model can cut per‑token costs by one to two orders of magnitude. A recent academic study (arXiv:2311.10466) found that routing reduces LLM costs by 50–80% with negligible quality loss.\n2.3. Latency vs. Accuracy Trade‑Offs # Lightweight models respond in under one second; flagship models can take three to five times longer. In a real‑time chat setting, this delay is noticeable and frustrating. Yet many tasks—like generating a short commit message or reformatting a function—don\u0026rsquo;t need the latency penalty of a heavy model. By routing quick tasks to fast models and complex reasoning to slower, powerful ones, average response time drops dramatically. Early user studies show a 40–60% reduction in perceived wait time for typical development workflows.\n3. The Delegation Pattern: What It Is and How It Works # 3.1. The Router Agent # At the heart of delegation sits a lightweight orchestrator—a small model or a deterministic service that receives every user request. It classifies the request along several dimensions: task type (code generation, question answering, summarization), complexity (simple formatting vs. multi‑step reasoning), and required domain knowledge (e.g., Python vs. legal text). Once classified, the router dispatches the request to the best‑suited model.\nConsider a request: \u0026ldquo;Write a unit test for this Python function.\u0026rdquo; The router sees task_type = \u0026quot;code generation\u0026quot;, sub_type = \u0026quot;testing\u0026quot;, complexity = \u0026quot;moderate\u0026quot;. It sends the request to DeepSeek‑V3 for accuracy, not to a creative‑writing model. If the same user later asks \u0026ldquo;Explain why this recursion is inefficient,\u0026rdquo; the router detects a reasoning‑heavy question and routes it to Claude 4 Sonnet.\n3.2. Design Approaches # There are three common ways to build the router:\nRule‑based routing: Use keywords, prompt classifiers, or regular expressions to map requests. Simple, predictable, and ideal for well‑defined tasks (e.g., \u0026ldquo;if the request starts with \u0026lsquo;Write a test for\u0026rsquo;, route to DeepSeek‑V3\u0026rdquo;). The overhead is negligible.\nMachine‑learned routers: Train a lightweight classifier (e.g., logistic regression or a small BERT model) on historical request‑model performance data. This adapts dynamically as usage patterns evolve, but requires ongoing data collection.\nAgent‑style orchestration: Frameworks like LangGraph and Anthropic\u0026rsquo;s agent patterns allow multi‑step workflows. The router might call a small model first for a quick answer, then escalate to a larger model if confidence is low. It can also chain models—e.g., generate code with DeepSeek‑V3, then run it through a syntax checker, then format the output with GPT‑4o mini.\n3.3. Fallback \u0026amp; Quality Assurance # No router is perfect. When the primary model produces low‑confidence output—detected via log‑probability scores, output length anomalies, or explicit self‑checks—the orchestrator escalates to a fallback model. For example, a rule‑based router might send a \u0026ldquo;complex reasoning\u0026rdquo; question to GPT‑4o mini by mistake. The mini model\u0026rsquo;s answer has low probability; the orchestrator re‑routes to Claude 4 Opus for a high‑quality response. This safety net ensures that quality never dips below acceptable thresholds.\n4. Real‑World Benefits # 4.1. Cost Reduction # Consider a typical development environment: 70% of requests are simple (linting, formatting, short completions) and can be handled by cheap models at $0.15/M tokens. The remaining 30% require frontier intelligence at $12/M tokens. The blended cost becomes roughly:\n0.70 × $0.15 + 0.30 × $12 = $0.105 + $3.60 = $3.705/M tokens Compare that to $12/M tokens when using a single expensive model for everything: a saving of 69%. In practice, many teams report even larger savings by routing more aggressively—up to 92% in internal case studies. The academic literature (arXiv:2311.10466) confirms that routing reduces costs by 50–80% with less than 2% quality degradation.\n4.2. Improved Quality \u0026amp; Task Fit # When the right model handles the right job, user satisfaction rises. Code reviews routed to DeepSeek‑V3 catch subtle bugs that GPT‑4.1 might miss, while creative copy routed to Claude 4 Sonnet produces more eloquent prose. The overall user experience improves because each model\u0026rsquo;s strengths are exploited. In a controlled A/B test, one team saw a 12% increase in code acceptance rates after introducing routing.\n4.3. Faster Time‑to‑Response # Parallel execution is a game changer. While a reasoning model works on a complex logic problem, a small model can format the output live. For multi‑step tasks (e.g., \u0026ldquo;generate a Django view and write tests for it\u0026rdquo;), the router can send the view generation to a code specialist and the test generation to a test‑focused model simultaneously. Average response time drops by 40–60% for typical development workflows.\n5. Implementing Model Delegation in Your Development Toolchain # 5.1. Choosing a Router / Orchestrator # You have several options:\nOpen‑source frameworks: LangGraph (orchestration with state machines), Custodian (router‑first framework), and OpenRouter (API‑level routing that handles model selection automatically).\nProprietary solutions: OpenAI\u0026rsquo;s function calling pattern and Anthropic\u0026rsquo;s agent patterns both support routing multi‑step tasks.\nSelf‑built: A lightweight Python or TypeScript service that calls model provider SDKs. This gives full control over routing logic and fallback policies.\nFor teams just starting, OpenRouter offers the quickest path: send a single request, and it chooses the best model based on latency/price/quality preferences. As you grow, a custom router using LangGraph or Custodian gives finer control.\n5.2. Integration Patterns # IDE plugins: Route requests from Copilot‑like assistants or Continue.dev. The plugin sends the code context to the orchestrator, which dispatches to the best model and returns the completion.\nCI/CD pipelines: Automatically route code review, test generation, and documentation tasks. For example, a push triggers a pull‑request review that sends diff analysis to DeepSeek‑V3 and summarisation to GPT‑4o mini.\nChat interfaces: Build a single endpoint that hides the model zoo from the user. The user types a question; the orchestrator picks the model and returns the answer. This is the pattern used by tools like Perplexity AI.\n5.3. Monitoring \u0026amp; Iteration # Routing is not a set‑and‑forget pattern. Track per‑model usage, cost, latency, and error rates. Use A/B testing to compare routing rules: route 50% of requests with one configuration and 50% with another, then measure quality scores. Over time, you can tune the classifier thresholds, add new models, and retire underperformers.\n6. Challenges \u0026amp; Pitfalls to Avoid # 6.1. Router Inaccuracy # The biggest risk: misclassifying a challenging reasoning task as a simple one and sending it to a weak model. This degrades quality. Mitigate with confidence thresholds: if the router\u0026rsquo;s certainty is below, say, 0.8, defer to a human‑in‑the‑loop or escalate to a flagship model. For high‑stakes tasks (e.g., contract review), always use a strong model despite the cost.\n6.2. Added Latency Overhead # The orchestration step itself takes 50–200ms on average—negligible for most background tasks, but noticeable in real‑time chat. Mitigation: cache routing decisions for frequent request patterns (e.g., \u0026ldquo;generate a Django view\u0026rdquo; always routes to the same model). Also consider pre‑computing routing rules for known user intents.\n6.3. Model Deprecation \u0026amp; API Changes # Models come and go. A router hard‑coded to \u0026ldquo;gpt‑4‑turbo‑2024‑04‑09\u0026rdquo; will break when the API deprecates that version. Use dynamic provider APIs like OpenRouter that abstract versioning, or maintain a registry that maps task types to model names, updated weekly.\n6.4. Privacy \u0026amp; Data Residency # Routing requests to different providers may send data to jurisdictions that violate your compliance policies. Maintain an internal whitelist of allowed models based on data‑residency requirements. For sensitive code, use on‑premise models (e.g., Llama 4‑70B via vLLM) and never route to external APIs.\n7. Prototyping the Concept: AI Dispatch # All of this sounds compelling in theory, but does it hold up in practice? To find out, I built AI Dispatch — an open-source MCP server that brings this exact delegation pattern to OpenCode, a CLI-native AI coding assistant. Think of it as a reference implementation: a lightweight, local‑first orchestrator that validates every claim from sections 2 through 5 with real running code.\n7.1. Architecture Overview # AI Dispatch is an MCP orchestrator written in TypeScript (Node 24, ESM). It exposes a set of tools (agent/run, agent/delegate, task/status, kb/read, etc.) over the Model Context Protocol — the same protocol used by VS Code Agent mode, Copilot, and OpenCode. The server runs as a child process via stdio, or remotely via SSE with optional OAuth2.\nThe flow works like this:\nA request arrives from OpenCode or a CI trigger.\nThe orchestrator agent — a small, fast model (DeepSeek V4 Flash) — classifies the intent using a decision prompt.\nIt calls agent/run to dispatch the task to the appropriate specialist agent.\nThe specialist agent runs with its own model, system prompt, and tool permissions.\nIf configured, a mirror auditor validates the output and requests revisions if needed.\nResults are written to the shared knowledge base at _kb/outbox/ for consolidation.\n7.2. The Model Tiering Strategy # Every agent in the system has its own model assignment, configured declaratively in .agent.md files:\nAgent Model Role Cost Tier Orchestrator (router) DeepSeek V4 Flash Intent classification, tool orchestration Cheap code-review Claude Sonnet 4 Deep code analysis, bug detection Premium code-review-auditor Claude Sonnet 4 Mirror validation of review output Premium docs-sync GPT-4o mini Documentation formatting, changelogs Cheap incident-response Claude Sonnet 4 Triage, RCA, postmortems Premium onboarding GPT-4o mini Onboarding plan generation Cheap This is exactly the tiered model strategy from section 2 — applied in real configs, not just slides. The cheap routing model handles classification and tool calls (\u0026lt; $0.15/M tokens), while premium models handle code reasoning and auditing (~$12/M tokens). The right tool for each job.\n7.3. Walkthrough: A Code Review from Prompt to Result # The best way to understand the delegation pattern is to trace a single request end-to-end. Here is how a code review flows through AI Dispatch:\nStep 1 — User prompt: The developer types \u0026ldquo;Review this pull request\u0026rdquo; in OpenCode with a diff attached.\nStep 2 — Orchestrator classifies: The orchestrator agent (DeepSeek V4 Flash) reads the prompt, identifies domain = \u0026quot;code review\u0026quot;, and decides this maps to the code-review agent. It calls:\nagent/run({ agent: \u0026#34;code-review\u0026#34;, input: { diff: \u0026#34;...\u0026#34;, files: [...] } }) Step 3 — Task enqueued: The MCP server loads the code-review agent config from agents/code-review.agent.md, resolves its model (Claude Sonnet 4, temperature 0.3), and enqueues a task.\nStep 4 — Code review runs: The agent receives the diff, analyses it for bugs, security issues, and style violations, and writes a structured report to _kb/outbox/review-{task-id}.md.\nStep 5 — Mirror audit: The code-review.agent.md config declares mirror: code-review-auditor. Once the primary agent completes, the orchestrator automatically invokes the auditor with the primary\u0026rsquo;s input and output. The auditor checks for incomplete findings, misassigned severity, and false positives.\nStep 6 — Revision cycle: If the auditor returns needs-revision with feedback (e.g., \u0026ldquo;Missing analysis on the authentication middleware\u0026rdquo;), the orchestrator retries the code-review agent — passing the audit feedback as context. This loop repeats up to maxRetries (configured to 2).\nStep 7 — Consolidation: The orchestrator reads the final report from _kb/outbox/ and presents it to the developer.\nThis is not a hypothetical architecture diagram — this is the actual code path in the packages/mcp-orchestrator/src/ source tree. The mirror/ directory implements the retry loop, the dag/ directory handles multi-step workflows, and the queue/ directory manages task lifecycle.\n7.4. Multi-Step Workflows (DAGs) # Single-agent routing is powerful, but the real leverage comes from chaining agents together. AI Dispatch supports config-driven DAGs — directed acyclic graphs of dependent tasks that execute in topological order with automatic parallel fan-out.\nFor example, a full \u0026ldquo;review and document\u0026rdquo; workflow:\n{ \u0026#34;agent\u0026#34;: \u0026#34;review-and-document\u0026#34;, \u0026#34;dag\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;review\u0026#34;, \u0026#34;agent\u0026#34;: \u0026#34;code-review\u0026#34;, \u0026#34;input\u0026#34;: { \u0026#34;diff\u0026#34;: \u0026#34;...\u0026#34; } }, { \u0026#34;id\u0026#34;: \u0026#34;docs\u0026#34;, \u0026#34;agent\u0026#34;: \u0026#34;docs-sync\u0026#34;, \u0026#34;input\u0026#34;: \u0026#34;{{review.output}}\u0026#34;, \u0026#34;depends_on\u0026#34;: [\u0026#34;review\u0026#34;] }, { \u0026#34;id\u0026#34;: \u0026#34;notify\u0026#34;, \u0026#34;agent\u0026#34;: \u0026#34;meeting-prep\u0026#34;, \u0026#34;input\u0026#34;: \u0026#34;{{docs.output}}\u0026#34;, \u0026#34;depends_on\u0026#34;: [\u0026#34;docs\u0026#34;] } ] } The orchestrator validates the DAG (cycle detection via topological sort), executes ready nodes in parallel, and persists the run to _kb/sessions/ for traceability.\nBecause each node can use a different model, this is a concrete implementation of the tiered delegation pattern: the expensive model handles deep reasoning in step 1, then cheaper models handle formatting and notification in steps 2 and 3.\n7.5. Integration as the Default OpenCode Agent # The project\u0026rsquo;s opencode.json config sets the orchestrator as the default agent:\n{ \u0026#34;default_agent\u0026#34;: \u0026#34;orchestrator\u0026#34;, \u0026#34;agent\u0026#34;: { \u0026#34;orchestrator\u0026#34;: { \u0026#34;mode\u0026#34;: \u0026#34;primary\u0026#34;, \u0026#34;model\u0026#34;: \u0026#34;openrouter/deepseek/deepseek-v4-flash\u0026#34;, \u0026#34;prompt\u0026#34;: \u0026#34;{file:.opencode/prompts/orchestrator.txt}\u0026#34; }, \u0026#34;code-review-agent\u0026#34;: { \u0026#34;model\u0026#34;: \u0026#34;openrouter/anthropic/claude-opus-4.8\u0026#34;, \u0026#34;hidden\u0026#34;: true }, \u0026#34;docs-sync-agent\u0026#34;: { \u0026#34;model\u0026#34;: \u0026#34;openrouter/openai/gpt-4o-mini\u0026#34;, \u0026#34;hidden\u0026#34;: true } } } This means every request in OpenCode — whether it is a code review, a documentation update, or a general question — flows through the orchestrator first. The orchestrator decides whether to handle it directly (chat, project info) or dispatch it to a specialist agent via agent/run. The specialist agents are marked hidden: true so the user never sees them; the routing is transparent.\nThe MCP server is wired in .vscode/mcp.json and .copilot/mcp-config.json, making it available both in the IDE and in headless CLI mode. This is the dual-entry pattern from section 5 — the same orchestration engine powers interactive development and automated CI/CD pipelines.\n7.6. What This Validates (Projections \u0026amp; Local Benchmarks) # AI Dispatch is a concept project—a prototype designed to validate the routing pattern before committing to a production-scale implementation. While it lacks production metrics from hundreds of concurrent users, my initial local tests and simulations during a weekend of hacking confirm that intelligent delegation works:\nEstimated Cost Savings: Based on the distribution of my test queries where roughly 65–70% hit cheap models (GPT-4o mini, DeepSeek V4 Flash), the simulated blended per-token cost landed at about $3.50/M tokens. This represents a theoretical 71% saving compared to sending everything to Claude Opus.\nQuality \u0026amp; The Auditor Loop: During my local evaluation scenarios, the mirror protocol successfully caught incomplete findings or missed edge cases on the first pass. It demonstrates that a programmatic retry loop is perfectly viable for automated code reviews.\nPerceived Latency: Simple tasks (documentation formatting, onboarding plans) complete in under 2 seconds. Complex reviews take 10–15 seconds due to the multi-model chain—but the user gets an immediate, fast response on the vast majority of standard interactions.\nRouter Accuracy: The prompt-based orchestrator classifier proved highly effective for well-defined domains. Misrouting happened in only a small fraction of my test cases, and the fallback mechanism was able to handle these gracefully.\nThe project is not production-hardened—it lacks structured logging, metrics dashboards, and horizontal scaling. But it successfully proves that intelligent delegation is not just a theoretical cost-saving exercise. It is practical, highly flexible, and can be built with modest effort using existing MCP infrastructure.\n8. The Future: From Delegation to Autonomy # By 2027, the conversation will shift from \u0026ldquo;which model?\u0026rdquo; to \u0026ldquo;which agentic workflow?\u0026rdquo; Gartner\u0026rsquo;s Hype Cycle places agent‑based orchestration just entering the plateau of productivity. Self‑improving routers that learn from usage patterns and automatically tune delegation rules are already on the horizon. We\u0026rsquo;ll see multi‑agent swarms where parallel specialised models collaborate on complex software projects—one model writes tests, another refactors code, a third checks security vulnerabilities.\nDevelopers will evolve from direct users of individual models to AI orchestrators: they define the workflow, set quality and cost budgets, and let the router handle the allocation. This human‑AI symbiosis is the natural next step in building cost‑effective, high‑quality AI‑assisted development.\nSummary # Model abundance is here to stay, but so is choice paralysis. Intelligent delegation—using a router to send each task to the model best suited for it—solves the cost, latency, and quality mismatches of using a single \u0026ldquo;super‑model\u0026rdquo; for everything. By understanding model specialisation, implementing a lightweight orchestrator, and monitoring performance, teams can slash costs by 50–80%, improve response times, and boost output quality. The future belongs not to the biggest model, but to the smartest delegation.\nAnd as the AI Dispatch prototype demonstrates, this future is already buildable — with an MCP server, a handful of agent config files, and a clear routing strategy.\nSources # State of AI in 2026 – McKinsey — AI adoption rates and market expansion data.\nLMSYS Chatbot Arena Leaderboard — Real‑world model performance rankings across tasks.\nOpenRouter Model Comparison — Pricing and capabilities comparison across providers.\nHumanEval Coding Benchmark Results — Code generation performance metrics.\nMMLU-Pro Benchmark – Model Knowledge Comparison — Knowledge and reasoning benchmarks.\nAnthropic\u0026rsquo;s Agent Design Patterns — Best practices for building AI agents.\nLangGraph Multi‑Agent Systems — Architecture patterns for model orchestration.\nOpenAI Function Calling \u0026amp; Tool Use — How to route tasks programmatically.\nReducing LLM Costs through Routing — Academic paper on model selection cost optimization.\nAI Model Economy – Andreessen Horowitz — The shift toward specialised vs. generalist AI models.\nGartner AI Hype Cycle 2026 — Market phase analysis for AI technologies.\nAI Model Pricing Trends 2026 – Artificial Analysis — Model cost/performance analysis and adoption statistics.\nAI Dispatch – Open-Source MCP Orchestrator — The reference implementation discussed in section 7.\nModel Context Protocol Specification — MCP standard for tool and resource exposure.\n","date":"28 juin 2026","externalUrl":null,"permalink":"/the-ai-orchestrator-why-intelligent-delegation-is-the-missing-piece-in-your-ai-toolchain/","section":"Posts","summary":"","title":"The AI Orchestrator: Why Intelligent Delegation is the Missing Piece in Your AI Toolchain","type":"posts"},{"content":"","date":"15 juin 2026","externalUrl":null,"permalink":"/tags/copilot/","section":"Tags","summary":"","title":"Copilot","type":"tags"},{"content":"","date":"15 juin 2026","externalUrl":null,"permalink":"/tags/debugging/","section":"Tags","summary":"","title":"Debugging","type":"tags"},{"content":" Introduction: The Black Box of AI Code Generation # When you ask GitHub Copilot to write a function, refactor a module, or explain a complex piece of code, the response you get is the output of a probabilistic model. Unlike a traditional deterministic program—where the same input always produces the same output—an LLM (Large Language Model) generates each token based on a probability distribution over its vocabulary. The same prompt can yield different completions across invocations, and the internal reasoning that led to a particular choice of tool or sequence of steps is opaque.\nThis black‑box nature poses a fundamental challenge for developers who need to trust, debug, or audit Copilot’s behaviour. How do you know which tools the LLM actually invoked? How was the prompt assembled from your context and your question? Did the agent follow the intended chain of thought, or did it take a shortcut that could lead to an incorrect deployment or a security risk?\nObservability offers a way to lift the lid on that black box. By capturing structured telemetry—traces, spans, and attributes—we can see exactly what the LLM did: which tools were called, in what order, with what parameters, and how the final response was constructed. This article presents two practical approaches to gaining that visibility: the built‑in debug tools in VS Code and a full‑fledged OpenTelemetry (OTEL) pipeline. Both are accessible to senior developers and can be set up with minimal overhead.\nUnderstanding LLM \u0026ldquo;Thinking\u0026rdquo; vs. Observability # What \u0026ldquo;Thinking\u0026rdquo; Means for a Probabilistic Model # It is tempting to anthropomorphize LLMs and talk about them “thinking” or “reasoning.” In reality, an LLM does not reason in the human sense; it generates token sequences by repeatedly sampling from a probability distribution conditioned on the input prompt and the tokens generated so far. The “thinking” we can observe is not the internal token‑by‑token process (which remains hidden unless we explicitly capture the raw prompt and response text), but rather the high‑level decisions the model makes about which tools to invoke, in what order, and how to assemble context.\nFor example, when a user asks “Deploy to production,” the LLM might decide to call a bash tool to run a deployment script, then call a read_bash tool to check the output, and finally invoke an enterprise skill that enforces a deployment approval workflow. Each of these steps is a discrete action that can be recorded in a trace. The chain‑of‑thought prompting that sometimes appears in the response (e.g., “First, I will check the current branch…”) is part of the generated text; it is not directly visible as a separate span unless you have enabled content capture and the LLM includes it in the response.\nWhat Observability Reveals (and What It Doesn\u0026rsquo;t) # Visible:\nWhich tools were called, in what order, and with what parameters (e.g., the command string passed to bash).\nHow the prompt was constructed from the user’s input, the current editor context, and any retrieved files or snippets.\nAgent invocations: which agent was used (e.g., workspace, chat, custom_agent) and the operation type.\nTiming information: how long each tool call or agent step took.\nNot visible:\nThe internal token‑by‑token generation process (unless content capture is enabled, which records the full prompt and response text).\nWhy the LLM chose one tool over another—only the outcome is recorded. For instance, you can see that bash was called, but not that it was selected because the LLM “thought” it was the most appropriate tool.\nThe probability distribution or confidence scores for each token. Those are not exposed in the current Copilot instrumentation.\nUnderstanding these boundaries is critical: observability gives you a detailed log of what happened, but it does not explain the model’s internal reasoning. It is a diagnostic tool, not a mind‑reading device.\nMethod 1: VS Code\u0026rsquo;s Built-in LLM Debug Tools # VS Code provides a developer debug window that lets you inspect the chat history and see how the LLM processed your prompts and context. To access it:\nOpen the Command Palette (Ctrl+Shift+P or Cmd+Shift+P).\nRun “Developer: Toggle Developer Tools.”\nIn the Developer Tools panel, switch to the “Console” tab and filter for messages from the Copilot extension.\nLook for log entries that show the full chat history, including the system prompt, user messages, and assistant responses.\nThis view shows the final interactions—what was sent to the model and what came back. It is useful for quick debugging when you want to see exactly what context was included or verify that a particular instruction was followed. However, it has significant limitations:\nIt only shows the final state, not the real‑time sequence of tool calls or agent steps.\nThere is no structured trace; you have to parse raw log output.\nIt does not capture metrics or span relationships.\nIt works only within VS Code, not for the Copilot desktop app or other editors.\nWhen to use this approach: when you need a fast, no‑dependency check—for example, to confirm that the LLM is seeing the correct file contents or to diagnose why a prompt was misinterpreted. For deeper analysis, you need the full observability pipeline.\nMethod 2: Full Observability with OpenTelemetry (Deep Dive) # Architecture Overview # The recommended architecture for capturing Copilot telemetry is:\nCopilot (VS Code or Desktop App) → OTLP Exporter (gRPC or HTTP) → OpenTelemetry Collector → Aspire Dashboard (or other backend) Copilot, when configured to export OpenTelemetry data, sends traces and spans to an OTLP endpoint. The OpenTelemetry Collector receives these traces, processes them (batch, filter, enrich), and forwards them to a visualization backend. For local development, the Aspire Dashboard (part of .NET Aspire) provides a simple, self‑contained UI that displays traces, spans, and metrics.\nKey environment variables control this pipeline:\nCOPILOT_OTEL_ENABLED: set to true to enable OpenTelemetry export.\nOTEL_EXPORTER_OTLP_ENDPOINT: the URL of the OTLP receiver (e.g., http://localhost:4317 for gRPC, http://localhost:4318 for HTTP).\nCOPILOT_OTEL_CAPTURE_CONTENT: set to true to include the full prompt and response text in span attributes. Use with caution—this can generate very large traces and may expose sensitive code.\nNote: Copilot’s OpenTelemetry integration is currently in preview. Ensure you are using a compatible version of VS Code and the Copilot extension. Preview features may change, have limited support, or require specific versions. For the latest details, see the official GitHub Copilot Telemetry (Preview) documentation.\nConfiguration: VS Code # In VS Code, you configure OpenTelemetry through the settings.json file. Add the following keys under github.copilot.chat.otel.*:\n{ \u0026#34;github.copilot.chat.otel.enabled\u0026#34;: true, \u0026#34;github.copilot.chat.otel.endpoint\u0026#34;: \u0026#34;http://localhost:4317\u0026#34;, \u0026#34;github.copilot.chat.otel.captureContent\u0026#34;: true } Replace the endpoint with your collector’s address. If you use HTTP instead of gRPC, change the port to 4318 and ensure the collector is configured accordingly.\nThese settings take effect immediately; no restart is required. You can verify that traces are being sent by checking the VS Code “Output” panel for the Copilot channel—it will log a message like “OpenTelemetry exporter started.”\nConfiguration: Copilot Desktop App (macOS) # The Copilot desktop app (for macOS) does not have a settings UI for OpenTelemetry. Instead, you must set environment variables via a LaunchAgent plist file. This is a macOS‑specific approach; Windows and Linux users should refer to the platform alternatives (see the Practical Considerations section).\nStep‑by‑step plist creation:\nCreate a plist file at ~/Library/LaunchAgents/com.github.copilot.otel.plist with the following content: \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;Label\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;com.github.copilot.otel\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;ProgramArguments\u0026lt;/key\u0026gt; \u0026lt;array\u0026gt; \u0026lt;string\u0026gt;/usr/bin/open\u0026lt;/string\u0026gt; \u0026lt;string\u0026gt;/Applications/GitHub Copilot.app\u0026lt;/string\u0026gt; \u0026lt;/array\u0026gt; \u0026lt;key\u0026gt;EnvironmentVariables\u0026lt;/key\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;COPILOT_OTEL_ENABLED\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;true\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;OTEL_EXPORTER_OTLP_ENDPOINT\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;http://localhost:4317\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;COPILOT_OTEL_CAPTURE_CONTENT\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;false\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;key\u0026gt;RunAtLoad\u0026lt;/key\u0026gt; \u0026lt;true/\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; Load the LaunchAgent:\nlaunchctl load ~/Library/LaunchAgents/com.github.copilot.otel.plist The environment variables will take effect the next time the app is launched.\nRestart the Copilot desktop app (quit and reopen).\nTo verify that the environment variables are set, you can run launchctl setenv COPILOT_OTEL_ENABLED true (temporary) or check the process environment via ps eww $(pgrep -f \u0026quot;GitHub Copilot\u0026quot;).\nNote: This method only works on macOS. For Windows, set system‑wide environment variables via “System Properties → Environment Variables” or use a startup script. For Linux, use a systemd service override or a shell wrapper that exports the variables before launching the app.\nLocal Collector Setup with Docker # To collect and visualize traces locally, you need an OpenTelemetry Collector and a dashboard. The simplest setup uses Docker Compose with the OpenTelemetry Collector Contrib distribution and the Aspire Dashboard.\nCreate a docker-compose.yml file:\nversion: \u0026#39;3.8\u0026#39; services: otel-collector: image: otel/opentelemetry-collector-contrib:latest command: [\u0026#34;--config=/etc/otel-collector-config.yaml\u0026#34;] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - \u0026#34;4317:4317\u0026#34; # gRPC - \u0026#34;4318:4318\u0026#34; # HTTP depends_on: - aspire-dashboard aspire-dashboard: image: mcr.microsoft.com/dotnet/aspire-dashboard:latest ports: - \u0026#34;18888:18888\u0026#34; # Dashboard UI - \u0026#34;4319:4319\u0026#34; # OTLP ingestion (if needed) environment: - DOTNET_ENVIRONMENT=Development Now create the collector configuration file otel-collector-config.yaml:\nreceivers: otlp: protocols: grpc: endpoint: \u0026#34;0.0.0.0:4317\u0026#34; http: endpoint: \u0026#34;0.0.0.0:4318\u0026#34; processors: batch: timeout: 1s send_batch_size: 1024 connectors: spanmetrics: dimensions: - name: gen_ai.operation.name default: unknown - name: gen_ai.tool.name default: unknown - name: github.copilot.tool.parameters.skill_name default: unknown exporters: otlp/aspire: endpoint: \u0026#34;aspire-dashboard:4319\u0026#34; tls: insecure: true logging: loglevel: debug service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlp/aspire, logging] metrics: receivers: [spanmetrics] exporters: [logging] Key dimensions for LLM observability:\ngen_ai.operation.name: identifies the type of LLM operation (e.g., chat, completion, agent).\ngen_ai.tool.name: the name of the tool called (e.g., bash, read_bash, mcp_tool).\ngithub.copilot.tool.parameters.skill_name: for enterprise skills, the name of the skill invoked.\nThe spanmetrics connector generates metrics from trace data, allowing you to track tool usage frequency and operation types over time.\nStarting and Verifying the Setup # Run docker compose up in the directory containing the docker-compose.yml file.\nWait for the collector and dashboard to start (check logs for “Everything is ready”).\nOpen the Aspire Dashboard at http://localhost:18888.\nIn VS Code or the Copilot desktop app (with OTEL enabled), start a chat or use Copilot features. You should see traces appearing in the dashboard within a few seconds.\nThe dashboard will show a list of traces. Clicking on a trace reveals a waterfall view of spans, each with its attributes. For example, a chat session might have a root span “chat session” with child spans for each tool call. The span attributes will include the parameters passed to the tool, the duration, and (if content capture is enabled) the full prompt and response text.\nUnderstanding the Trace Data # What Each Span Represents # A typical Copilot trace contains the following span hierarchy:\nRoot span: represents a chat session or a single user interaction. Attributes include gen_ai.operation.name (e.g., chat), user.id (if available), and session.id.\nChild spans: each tool call or agent invocation gets its own span. For example:\ntool.call span with gen_ai.tool.name = bash and attributes like gen_ai.tool.parameters.command.\ntool.call span for read_bash with the output file path.\nagent.invoke span with gen_ai.operation.name = agent and github.copilot.tool.parameters.skill_name = deploy_approval.\nMCP tool calls: if Copilot uses the Model Context Protocol (MCP), a span with gen_ai.tool.name = mcp_tool and additional attributes like mcp.tool.server and mcp.tool.name.\nNote: The exact attribute names (e.g., gen_ai.tool.parameters.command) may vary slightly depending on the Copilot version and instrumentation. For the most up‑to‑date schema, consult the official GitHub Copilot Telemetry (Preview) documentation.\nInterpreting the \u0026ldquo;Thinking\u0026rdquo; Flow # By reading the sequence of spans in a trace, you can reconstruct the LLM’s decision path. Consider this example:\nUser asks: “Deploy to production.”\nRoot span: chat session.\nChild span: tool.call → bash with command git status.\nChild span: tool.call → read_bash with output “On branch main, clean working tree.”\nChild span: tool.call → bash with command ./deploy.sh.\nChild span: agent.invoke → enterprise skill deploy_approval with parameters {environment: \u0026quot;production\u0026quot;}.\nFrom this trace, you can see that the LLM first checked the current branch, then decided to run a deploy script, and finally invoked an approval skill. The span attributes tell you the exact commands and parameters used. If content capture is enabled, you can also see the prompt that led to each tool call and the response that generated the next step.\nThis flow is not a record of the model’s internal “thoughts” but a precise log of the actions it took. It is invaluable for debugging unexpected behaviour, such as when the LLM calls a tool you did not intend or skips a critical validation step.\nMetrics from Traces # The spanmetrics connector in the collector produces metrics from the trace data. These metrics can be exported to Prometheus, Grafana, or simply logged. Common metrics include:\nTool call count by gen_ai.tool.name: how often each tool was used.\nOperation type distribution by gen_ai.operation.name: proportion of chat vs. agent vs. completion spans.\nSkill invocation frequency by github.copilot.tool.parameters.skill_name: which enterprise skills are most used.\nDuration percentiles for tool calls and chat sessions.\nThese aggregated metrics help you understand usage patterns and identify performance bottlenecks or unexpected tool usage across your team.\nPractical Considerations # Performance Impact # Enabling OpenTelemetry export adds minimal overhead when content capture is off—the exporter batches spans and sends them asynchronously. However, enabling COPILOT_OTEL_CAPTURE_CONTENT can generate very large traces (prompts and responses can be thousands of tokens). This can increase memory usage in the collector and network bandwidth.\nRecommendation: Enable content capture only during targeted debugging sessions, not continuously. Use the batch processor in the collector to reduce the number of outgoing requests. For production use, consider sampling traces (e.g., keep 1% of traces) to reduce volume.\nSecurity and Privacy # The default collector configuration exposes ports 4317 and 4318 to the host. In a local development environment this is usually acceptable, but if you run the collector on a shared or production machine, you should:\nRestrict network access (e.g., bind to 127.0.0.1 instead of 0.0.0.0).\nAdd TLS encryption and an API key for the OTLP receiver.\nUse authentication middleware in the collector.\nContent capture includes potentially sensitive code, secrets, or proprietary information. Be mindful of data retention policies—consider setting a TTL on the collector’s memory or using a database that supports automatic deletion. Never leave content capture enabled in a shared environment without auditing what is being recorded.\nCross-Platform Limitations # VS Code configuration works identically on Windows, macOS, and Linux via settings.json.\nCopilot desktop app: the LaunchAgent approach is macOS‑only. On Windows, set environment variables via System Properties → Environment Variables (system‑wide) or use a batch script that launches the app with set COPILOT_OTEL_ENABLED=true. On Linux, use a systemd service override with Environment= directives, or a shell wrapper that exports the variables before running the app.\nCollector and dashboard: Docker Compose works on all platforms, but you may need to adjust network settings (e.g., on Windows, use host.docker.internal instead of localhost for the OTLP endpoint).\nAdvanced Topics and Alternatives # Alternative Visualization Tools # The Aspire Dashboard is convenient for local development, but you can replace it with any OpenTelemetry‑compatible backend:\nJaeger: a classic distributed tracing tool with powerful query capabilities and service dependency graphs. Use the Jaeger exporter in the collector.\nZipkin: similar to Jaeger, with a simpler UI.\nGrafana Tempo: a scalable, cost‑efficient trace storage backend that integrates with Grafana for dashboards.\nTrade‑offs: Aspire offers the simplest setup (single Docker image), while Jaeger and Tempo provide richer querying and aggregation features, especially for large trace volumes. For a team setting, consider using Grafana Tempo with the Grafana stack for unified metrics, logs, and traces.\nCorrelating Traces to Chat Sessions # To debug a specific user interaction, you need to map the trace to the chat session. The Copilot instrumentation typically includes a session_id or trace_id in the root span’s attributes. You can search for this ID in the dashboard or export traces to a log aggregation system. If your collector is configured to log span data, you can grep for the session ID in the collector’s output.\nFor deeper correlation, consider adding a custom attribute (e.g., user.id or chat.id) via the Copilot API if you are building a custom extension.\nExtending to Custom Agents and MCP Servers # If you have built custom agents or MCP servers that interact with Copilot, you can instrument them with OpenTelemetry to get end‑to‑end traces. For example:\nAdd span attributes to your MCP server’s tool handlers using the OpenTelemetry SDK for your language (Python, Node.js, Go, etc.).\nPropagate the trace context from Copilot’s OTLP export to your server via the traceparent header (if using HTTP) or the gRPC metadata.\nEnsure that spans from your custom agent appear as child spans under the Copilot chat session root span.\nThis allows you to see the full journey: from the user’s question, through Copilot’s tool selection, to your custom logic and back.\nSummary and Recommendations # When to use VS Code built‑in debug tools:\nQuick, one‑off debugging of a single chat interaction.\nNo external dependencies—works out of the box.\nLimited to seeing final prompt/response, not tool call sequences.\nWhen to use the full OTEL setup:\nYou need to understand the sequence of tool calls and agent invocations.\nYou want to aggregate metrics across multiple sessions or users.\nYou are debugging complex interactions involving enterprise skills or MCP tools.\nYou are building custom agents and need end‑to‑end trace correlation.\nQuick start guide for VS Code users:\nAdd the three github.copilot.chat.otel.* settings to your settings.json.\nRun the Docker Compose setup from the Local Collector Setup section.\nStart using Copilot—traces appear in Aspire Dashboard at http://localhost:18888.\nFull setup for teams:\nDeploy the OpenTelemetry Collector as a shared service (e.g., in a Kubernetes cluster or on a VM).\nUse a scalable backend like Grafana Tempo or Jaeger.\nEnable content capture only on demand, and implement retention policies.\nInstrument custom agents and MCP servers for unified observability.\nNext steps:\nExperiment with content capture to see the full prompt/response in traces.\nBuild metric dashboards in Grafana using the spanmetrics dimensions.\nExtend the setup to cover your own tools and agents.\nBy lifting the lid on Copilot’s black box, you gain the confidence to trust its outputs, diagnose failures, and optimise your AI‑assisted development workflow.\nSources # OpenTelemetry Documentation — used for collector configuration and OTLP protocol details.\nGitHub Copilot Telemetry (Preview) — official documentation for Copilot’s OpenTelemetry export (environment variables and settings).\n.NET Aspire Dashboard — used for local trace visualization.\nOpenTelemetry Collector Contrib — reference for the spanmetrics connector and batch processor.\nVS Code Developer Tools — used for accessing the debug console.\n","date":"15 juin 2026","externalUrl":null,"permalink":"/lifting-the-lid-on-copilot-s-black-box-observability-for-llm-code-generation/","section":"Posts","summary":"","title":"Lifting the Lid on Copilot's Black Box: Observability for LLM Code Generation","type":"posts"},{"content":"","date":"15 juin 2026","externalUrl":null,"permalink":"/tags/llm/","section":"Tags","summary":"","title":"Llm","type":"tags"},{"content":"","date":"15 juin 2026","externalUrl":null,"permalink":"/tags/observability/","section":"Tags","summary":"","title":"Observability","type":"tags"},{"content":"","date":"15 juin 2026","externalUrl":null,"permalink":"/tags/open-telemetry/","section":"Tags","summary":"","title":"Open-Telemetry","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/ai-agent/","section":"Tags","summary":"","title":"Ai-Agent","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/ai-engineering/","section":"Tags","summary":"","title":"Ai-Engineering","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/benchmarking/","section":"Tags","summary":"","title":"Benchmarking","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/enterprise-ai/","section":"Tags","summary":"","title":"Enterprise-Ai","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/llm-observability/","section":"Tags","summary":"","title":"Llm-Observability","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/machine-learning/","section":"Tags","summary":"","title":"Machine-Learning","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/software-engineering/","section":"Tags","summary":"","title":"Software-Engineering","type":"tags"},{"content":"","date":"31 mai 2026","externalUrl":null,"permalink":"/tags/token-optimization/","section":"Tags","summary":"","title":"Token-Optimization","type":"tags"},{"content":"We started the same way everyone does: give the LLM access to everything and hope it figures it out. Connect the GitHub MCP, the Jira MCP, the internal product API MCP, throw in a database schema or two, and let the model roam.\nIt worked, sort of. The model could answer questions. It could perform actions. But every session was unpredictable. It called the wrong tool, cycled through irrelevant APIs, hallucinated endpoint parameters, and burned tokens on data it never needed.\nA few weeks ago, we started building something different: a harness. Not just more tools, but a curated ecosystem with a proxy that watches everything, a benchmark engine that measures what matters, and a learning loop that turns raw captures into measurable improvements.\nThe early results are already interesting, and we\u0026rsquo;re just getting started.\nPrinciple 1: An MCP Over a Product API Is Not a UX # The easiest thing in the world is to wrap an internal API in an MCP server and call it done. You generate an OpenAPI spec, point a tool at it, and the LLM now has access to 47 endpoints.\nDoes it help? Marginally. The model sees function signatures instead of REST paths. But it still needs to figure out which endpoint to call, in what order, with what parameters, and what to do when it fails.\nAn MCP is not a user interface. An MCP is a transport layer. The value doesn\u0026rsquo;t come from exposing more endpoints, it comes from designing operations that match what the LLM actually wants to accomplish.\nReal Example: E-Commerce Cart # Your product API has:\nPOST /cart/items — add an item\nPUT /cart/items/{id} — update quantity\nDELETE /cart/items/{id} — remove an item\nPOST /cart/checkout — start checkout\nGET /cart/delivery-options — available shipping\nPOST /cart/delivery-option — select shipping\nPOST /cart/payment — submit payment\nGET /cart/order-confirmation — get receipt\nThe LLM doesn\u0026rsquo;t want to orchestrate an 8-step checkout flow. It wants one MCP tool called complete_checkout(cart_id, shipping_method, payment_token) → order_summary that handles the orchestration internally. The raw API endpoints are available for the humans on the frontend team. The MCP should expose intents, not endpoints.\nThe rule: if an MCP tool signature looks like it was copied from a Swagger UI, you\u0026rsquo;re doing it wrong. An MCP should simplify the model\u0026rsquo;s cognitive load, not mirror the API\u0026rsquo;s internal structure. If you want to use raw APIs, create a SKILL, add the OpenAPI doc as sources and the LLM will know what to do without creating an extra MCP.\nPrinciple 2: Skills Are Process Descriptors, Not Code Snippets # Raw tools tell the LLM what it can do. A Skill tells it how to do something: the process, the order, the error recovery, the edge cases to check.\n# internal-app-referential Retrieves asset information from the product registry using direct GraphQL queries. ## When to use - User asks about products, components, or their relationships - User needs technology stack information across the portfolio - User asks for domain-level views (finance, hr, ecommerce, ...) ## Process 1. Call `read_skill` to load these instructions 2. Use `http_request` to call the GraphQL endpoint 3. For simple counts: use the `aggregateProductCount` query 4. For domain filtering: add the `domain` argument 5. For technology queries: use the `componentsWithStack` query 6. If HTTP 4xx or 5xx: retry once, then report the error to the user ## Important - Always query for the specific fields requested — don\u0026#39;t fetch everything - Paginate when querying lists (20 per page) - If a query returns no results, report \u0026#34;no data found\u0026#34; — don\u0026#39;t guess This can be part of a real skill to let the LLM navigate through a product referential. It\u0026rsquo;s not code, it\u0026rsquo;s instruction design. The LLM reads it, internalises the process, and executes it with the tools available.\nWhy Size Constraints Matter # We all learned this the hard way. Early skills were 3000+ word essays covering every possible variation, trying to script the unpredictable world of an LLM into a single predetermined path. Do we really need AI in that case? The LLM would lose track of the actual process inside all the documentation when the size is increasing.\nEvery skill follows a context budget now:\nName + Description: fits in the system prompt\u0026rsquo;s skill listing (1-2 lines each)\nFull instructions: loaded on-demand via read_skill, must fit in ~15 agentic steps\nOne process per skill: no branching into unrelated workflows\nIf a skill exceeds these constraints, it gets split. The LLM\u0026rsquo;s working memory is finite, treat it that way.\nPrinciple 3: In June 2026, Every Token Has a Price Tag # The API world has largely moved to pay-per-token pricing. Monthly subscription packages still exist for some consumer-facing products (GitHub Copilot, for example), but the major LLM providers: OpenAI, Anthropic, Google, charge per token consumed. \\[1\\] \\[2\\] \\[3\\] \\[4\\]\nFor API access, there\u0026rsquo;s no predictable monthly bill anymore, just meters running on every request, every system prompt, every tool schema, every verbose response.\nProvider Input ($/1M tokens) Output ($/1M tokens) Notes OpenAI GPT-5.2 $1.75 $14.00 OpenAI GPT-5 Mini $0.25 $2.00 Low-cost tier Claude Sonnet 4.6 $3.00 $15.00 Long-context premium above 200K input Gemini 2.5 Pro $1.25 $10.00 Higher rates above 200K input GitHub Copilot Pro $10/month flat seat Usage limits apply; pausing new sign-ups as of April 2026 \\[5\\] GitHub Copilot is changing its individual plans on June 1, 2026. \\[5\\] The company announced tighter usage limits, model availability changes (Opus removed from Pro plans), and a shift toward token-based weekly limits. If you\u0026rsquo;re using Copilot as part of your harness, this is worth watching.\nEvery token in every system prompt, every tool schema injected, every verbose API response: you pay for it.\nThis changes how you design your harness. When the AI wants a customer name, giving it the full customer object (address, preferences, order history, payment methods, 15 nested relationships) is not just bad design, it\u0026rsquo;s expensive bad design.\nYour Original APIs Need to Be AI-Ready Too # Here\u0026rsquo;s the part that took me a while to articulate clearly: it\u0026rsquo;s not just the MCP layer that needs rethinking. Your product APIs themselves should be designed with LLM usage in mind.\nAn LLM calling a standard REST endpoint gets everything back: every field, every nested object, every related resource. That\u0026rsquo;s fine for a frontend that can display what it needs and ignore the rest (is fine, but your anyway wasting resource usage). For an LLM, every field is a token it has to process, and tokens cost money.\nThe solution isn\u0026rsquo;t always \u0026ldquo;add an MCP on top.\u0026rdquo; Sometimes it\u0026rsquo;s worth going back to the API itself and asking: what would this look like if an AI agent were the primary consumer?\nThe pattern we could use: either design dedicated \u0026ldquo;lookup\u0026rdquo; endpoints that an LLM could query to retrieve only a subset of the fields, or wrap existing APIs with a thin MCP layer that does the filtering and conversion:\n# What the product API exposes to humans query { customer(id: \u0026#34;123\u0026#34;) { name, email, address, phone, preferences, paymentMethods, orders { items, total, status, history } } } # What an LLM-friendly API could expose query { customer(id: \u0026#34;123\u0026#34;) { name # → \u0026#34;Marco Mornati\u0026#34; # That\u0026#39;s it. Just what the model asked for. } } The second query costs a fraction of the first in tokens, and the model gets exactly the information it needs without noise.\nThis applies beyond GraphQL: wherever your LLM makes API calls (REST, gRPC, anything), the principle is the same: query for what you need, not what exists. Either the API supports fine-grained field selection, or an MCP layer filters the response before it reaches the model. The MCP is often the right choice for existing products you can\u0026rsquo;t modify, but for new APIs, design them AI-native from the start.\nPrinciple 4: Observe Everything, Then Improve # You cannot optimise what you do not measure. This is where the proxy and benchmark tools can change everything.\nThe Proxy: A Transparent Observation Layer # The proxy sits between every LLM client and the upstream provider. Every request, response, tool call, timing metric, and token count is captured, without changing a single line of application code.\nThe proxy has two modes:\nServer mode: runs as an HTTP server. Point any OpenAI-compatible client at it, and every interaction is transparently captured.\nBenchmark/CLI mode: runs headless from YAML config files, executing prompts through the LLM with automated MCP/Skill execution, then saving everything for analysis.\nThe Benchmark: Unit Tests for AI Behavior # This is the part I\u0026rsquo;m most excited about. A benchmark config file looks like this:\nproject: my-app model: gpt-5-mini system_prompt: \u0026gt;- You are a helpful assistant. Skills provide specialised instructions. Always use `read_skill` to load skill instructions before acting. skills: - name: my-skill path: ./skills/my-skill/SKILL.md env: - GRAPH_URL - API_KEY max_iterations: 30 prompts: - text: \u0026#34;How many products and components do we have?\u0026#34; asserts: - type: tool_called tool: read_skill times: 1 - type: tool_called tool: http_request times_min: 1 - type: tool_result_not_contains tool: http_request value: \u0026#34;HTTP 4\u0026#34; - type: response_contains value: \u0026#34;product\u0026#34; - type: response_contains value: \u0026#34;component\u0026#34; - text: \u0026#34;Which services have no monitoring? Flag any Tier-1 ones.\u0026#34; asserts: - type: tool_called tool: read_skill times: 1 - type: tool_called tool: http_request times_min: 1 - type: tool_result_not_contains tool: http_request value: \u0026#34;HTTP 5\u0026#34; - type: response_contains value: \u0026#34;monitor\u0026#34; These are unit tests for AI behaviour. Each prompt has assertions that check:\nWas the right tool called? (tool_called)\nWas it called the right number of times? (times:, times_min:, times_max:)\nDid the tool return errors? (tool_result_not_contains: \u0026quot;HTTP 4\u0026quot;)\nDid the final response contain the expected information? (response_contains:)\nThe benchmark engine runs every prompt through the LLM, executes any tool calls the model makes (including MCP tools), evaluates every assertion, and produces a pass/fail report:\nRunning benchmark: my-app Model: gpt-5-mini | Prompts: 9 | Skills: 1 [1/9] \u0026#34;How many products and components do we have?\u0026#34; ✓ tool_called: read_skill (1) ✓ tool_called: http_request (min 1) ✓ tool_result_not_contains: http_request → \u0026#34;HTTP 4\u0026#34; ✓ response_contains: product ✓ response_contains: component Score: 1.0 / Assertions: 5 passed, 0 failed [2/9] \u0026#34;Which services have no monitoring?\u0026#34; ✓ tool_called: read_skill (1) ✓ tool_called: http_request (min 1) ✓ tool_result_not_contains: http_request → \u0026#34;HTTP 5\u0026#34; ✓ response_contains: monitor Score: 1.0 / Assertions: 4 passed, 0 failed ... Run score: 92.3 — passing Each capture is also scored automatically based on tool call quality:\nCondition Score Tool execution failed (error) 0.0 Empty result (\u0026quot;\u0026quot; or {}) 0.3 Duration \u0026gt; 30,000ms 0.2 1,000ms \u0026lt; Duration ≤ 30,000ms Linear decay 1.0 → 0.0 Duration ≤ 1,000ms 1.0 No tool calls 1.0 The run score is the average across all captures, giving you a single number (0–100) that tells you how well your harness is performing.\nThe Learning Loop # Here is where it gets powerful. The benchmark is not a one-shot validation — it\u0026rsquo;s a learning loop:\nStep 1: Run the benchmark. The proxy executes every prompt, saves every capture as a JSONL file in ~/.benchmark/.\nStep 2: Inspect the captures. Each capture is a JSON object containing the full request, response, every tool call the model made (with arguments, results, timing), and assertion results.\nStep 3: Analyse the failures. Why did the model call the wrong tool? Why did it get HTTP 400? Why did it skip the error recovery step? The raw captures tell you exactly what happened — no guessing.\nStep 4: Improve the skill. Edit the SKILL.md, clarify the process, add missing error handling, adjust the description to route better. Then hand the skill file AND the failing captures to an LLM and ask: \u0026ldquo;Here\u0026rsquo;s what went wrong. Fix it.\u0026rdquo;\nStep 5: Loop. Re-run the benchmark. Did the score improve? Did any previously passing prompts regress?\nThe goal is to run this loop regularly: benchmark, inspect, improve, re-benchmark. Even early iterations have shown us things we\u0026rsquo;d never have caught without the captures. The more consistent the loop, the faster the harness improves.\nAnd, if you don\u0026rsquo;t care a lot about your token (so far we could do it) you can ask to the LLM to do this loop autonomously with a stop KPI. It can runs for hours!!\nToken Costing in the Loop # The learning loop also accounts for cost. After each run, we measure:\nTotal tokens consumed (input + output)\nTokens per assertion passed: a cost-efficiency metric\nToken overhead per prompt: how many tokens were spent on tool schemas vs actual data\nWhen improving a skill, we track whether the fix reduced or increased token usage. Sometimes a more detailed skill instruction causes the model to call more tools, consuming more tokens. The dashboard flags these regressions so we can find the sweet spot between accuracy and cost.\nExample from the scoring engine:\nRun: asset-knowledge-graph-direct v3 → v4 Score: 85.3 → 92.1 (+6.8) Tokens/run: 12,450 → 14,220 (+14%) Cost/run: $0.032 → $0.037 Efficiency score: 6.8 / (14% token increase) = 0.49 pts per % cost Verdict: Acceptable improvement. Monitor for scope creep. Routing Jeopardy: Catch Ambiguity Before It Costs # One more feature worth mentioning: before a benchmark even runs, an optional routing jeopardy mode pre-computes which skill or MCP each prompt should route to. If two skills have descriptions similar enough to confuse the LLM (Jaccard similarity within 5 points), it flags a conflict.\nThis catches a surprisingly common problem: you add a new skill, its description overlaps with an existing one, and suddenly prompts start routing to the wrong skill. The jeopardy report tells you before the benchmark run finishes.\nLessons Learned # MCP servers are not UIs. A 1:1 wrapper over a product API adds marginal value. An MCP that exposes high-level intents, matching what the LLM actually asks, is worth ten times more.\nSkills need size limits. The LLM\u0026rsquo;s context window is generous but its attention is not. Keep skills focused on one process, keep instructions under 15 steps, and load them on-demand.\nMeasure before you optimise. Without a proxy and a benchmark, you\u0026rsquo;re flying blind. The captures will surprise you, the LLM calls tools you didn\u0026rsquo;t expect, skips steps you thought were clear, and burns tokens on data you never asked for.\nToken cost is a design constraint now. In the pay-per-token era, every byte in every system prompt has a price. Design your tool responses to return the minimum viable data. And remember: this applies to your original product APIs too, not just the MCP layer.\nThe learning loop is the actual product. The initial skill file is never right. What matters is how fast you can run the loop: benchmark → inspect → improve → re-benchmark. The earlier you start measuring, the sooner the harness improves.\nAssertions are your regression safety net. Every time we split a skill or rewrite instructions, the benchmark catches regressions. Without those assertions, we\u0026rsquo;d be guessing, which is especially risky when you\u0026rsquo;re still learning what \u0026ldquo;good\u0026rdquo; looks like.\nCopilot and similar seat-based tools are changing too. GitHub Copilot\u0026rsquo;s June 2026 plan changes remind us that even subscription products are adapting to the agentic era. Keep an eye on your tool costs: the pricing landscape is shifting fast.\nThe wild west of \u0026ldquo;give the LLM everything and hope\u0026rdquo; is behind us. The companies that will get the most value from AI in 2026 will be the ones that treat their tool harness with the same discipline they treat their test suite: curated, measured, benchmarked, and continuously improved.\nReferences # \\[1\\] Current AI API Pricing March 2026: OpenAI, Grok, Anthropic, Gemini — StackSpend (March 2026)\n\\[2\\] OpenAI API Pricing — OpenAI\n\\[3\\] Claude API Pricing — Anthropic\n\\[4\\] Google Vertex AI Generative AI Pricing — Google Cloud\n\\[5\\] Changes to GitHub Copilot Individual Plans — GitHub Blog (April 20, 2026, updated May 14, 2026)\n","date":"31 mai 2026","externalUrl":null,"permalink":"/your-ai-agent-deserves-a-tool-harness-not-a-wild-west/","section":"Posts","summary":"","title":"Your AI Agent Deserves a Tool Harness, Not a Wild West","type":"posts"},{"content":"","date":"5 mai 2026","externalUrl":null,"permalink":"/tags/claude-code/","section":"Tags","summary":"","title":"Claude-Code","type":"tags"},{"content":"","date":"5 mai 2026","externalUrl":null,"permalink":"/tags/gemini/","section":"Tags","summary":"","title":"Gemini","type":"tags"},{"content":"","date":"5 mai 2026","externalUrl":null,"permalink":"/tags/openai/","section":"Tags","summary":"","title":"Openai","type":"tags"},{"content":"Last month, I published a comparison: MCP Servers vs. CLI. Single server (GitHub), controlled test, clear conclusion: Native MCP wastes 99.7% on schema tax in typical sessions.\nBut that\u0026rsquo;s a lab test. In reality, I don\u0026rsquo;t run one MCP server. I run four: GitHub, Garmin, Stitch, Intervals.icu. 2 for my develoment sections and 2 I\u0026rsquo;m using to plan and follow my health and sport coaching. And sometimes I don\u0026rsquo;t take care about the MCP servers and I\u0026rsquo;m making my requests with all of them enabled. What about you? I guess you too have configured several MCP servers and then forgot about them.\nThis post takes the same question into the real world: Measure actual token burn across a multi-server setup where you actually work—not a proof of concept, but production data.\nHere\u0026rsquo;s the problem: every MCP server you enable injects its entire tool schema into every single request—regardless of whether you actually use it. And in the pay-per-use AI era, that invisible tax is costing you real money.\nThe Shift No One Warned Us About # Remember when AI APIs had monthly packages? Those days are gone. As of early 2026, the industry has fully transitioned to token-based pay-per-use pricing.\nThe big players have made this crystal clear:\nProvider Input ($/1M tokens) Output ($/1M tokens) Note OpenAI GPT-5.4 $2.50 $15.00 Cached: $0.25 Claude Sonnet 4.6 $3.00 $15.00 Long-context premium above 200K Gemini 2.5 Pro $2.00 $12.00 2x above 128K tokens Every token counts now. And here\u0026rsquo;s what nobody talks about: every MCP server you connect is silently burning tokens on every prompt.\nI Measured It Live on my Own AI Setup # I queried my own working (personal) environment through LeanProxy (my new tool) to get real numbers. Here\u0026rsquo;s what I found:\nMCP Server Tools Available Tokens per Request Garmin 100 ~10,000 GitHub 41 ~4,100 Stitch (Google) 12 ~1,200 Intervals.icu 10 ~1,000 Total 163 ~16,300 tokens That\u0026rsquo;s approximately $0.04-$0.08 per request just to have the tools available. Even if you only use GitHub twice in a session.\nThe Real Cost: 3 Working Sessions # We simulated three realistic workflows:\nMorning Sport Check (4 prompts) # garmin_get_stats → intervals_get_events → intervals_get_activity_intervals → intervals_add_or_update_event That\u0026rsquo;s 4 tool operations—but a real morning check isn\u0026rsquo;t just 4 prompts. You check stats, then ask: \u0026ldquo;Am I recovered?\u0026rdquo;, \u0026ldquo;What\u0026rsquo;s my training readiness?\u0026rdquo;, \u0026ldquo;Compare to last week?\u0026rdquo;, \u0026ldquo;Any warnings?\u0026rdquo;, \u0026ldquo;What intensity for today?\u0026rdquo;, \u0026ldquo;Check weather impact\u0026hellip;\u0026rdquo;, \u0026ldquo;Adjust tomorrow\u0026rsquo;s plan based on this\u0026hellip;\u0026rdquo;.\nMore realistically: 15 prompts × 16,300 tokens = ~244,500 tokens\nNative MCP: ~244,500 tokens\nWith LeanProxy: ~2,000 tokens\nYou save: ~99%\nDevelopment Session (5 prompts) # github_search_repositories → github_get_file_contents → stitch_list_projects → stitch_generate_screen_from_text → github_create_pull_request But wait—there\u0026rsquo;s no 5 prompts in a real development session. You open your IDE, ask for an issue, get the code. Then 10 more prompts: \u0026ldquo;fix this bug\u0026rdquo;, \u0026ldquo;add tests\u0026rdquo;, \u0026ldquo;refactor this\u0026rdquo;, \u0026ldquo;why is it failing?\u0026rdquo; Each one includes the full MCP schema. The GitHub/Stitch tools are only used twice, but you\u0026rsquo;re paying for all 163 tools on every single prompt.\nA more realistic breakdown for a 15-prompt session:\nPrompts 1-2: GitHub/Stitch operations (2 tool invocations)\nPrompts 3-15: Coding, debugging, refactoring (0 tool invocations)\nThat\u0026rsquo;s 15 prompts × 16,300 tokens (full schema) = 244,500 tokens just to have tools available.\nNative MCP: ~244,500 tokens\nWith LeanProxy: ~2,500 tokens\nYou save: ~99%\nFull Day (7 prompts) # garmin_get_training_readiness → intervals_get_events → stitch_list_projects → github_get_file_contents → stitch_generate_screen_from_text → garmin_log_food → github_push_files But that\u0026rsquo;s 7 tool operations across the day—not 7 prompts. A real day looks more like:\nMorning (prompts 1-3): Check Garmin, plan session in Intervals, review last week\nMid-day (prompts 4-12): \u0026ldquo;Why did my HR spike?\u0026rdquo;, \u0026ldquo;What was my zone distribution?\u0026rdquo;, \u0026ldquo;Am I recovered enough?\u0026rdquo;, \u0026ldquo;Plan tomorrow\u0026rsquo;s session\u0026rdquo;, \u0026ldquo;Adjust intensity based on sleep\u0026rdquo;\u0026hellip;\nEvening (prompts 13-15): Log food, review training effect, check Intervals for next week\nDev work (prompts 16-25): Code, bugfix, refactor\u0026hellip;\nThat\u0026rsquo;s 25 prompts × 16,300 tokens = ~407,500 tokens just to have your MCP tools loaded.\nNative MCP: ~407,500 tokens\nWith LeanProxy: ~4,000 tokens\nYou save: ~99%\nThe Cache Read Illusion # You might think: \u0026ldquo;But prompt caching! 90% discount!\u0026rdquo;\nIt doesn\u0026rsquo;t work that way. Cache hits still cost money—they\u0026rsquo;re not free:\nAnthropic (Claude Sonnet 4.6):\nCategory Price per 1M tokens Fresh input $3.00 Cache write (5 min) $3.75 (1.25x) Cache hit (read) $0.30 (0.1x) Output $15.00 OpenAI (GPT-4o):\nCategory Price per 1M tokens Fresh input $2.50 Cache hit $1.25 (0.5x) Output $10.00 Cache hits are NOT free—they\u0026rsquo;re just discounted. And MCP tool schemas are identical every request, so 100% cache hit means:\n16,300 tokens × cache cost = \u0026#34;effective\u0026#34; tokens still costing you With Claude Sonnet: 16,300 × $0.30/M = ~$0.005/request With GPT-4o: 16,300 × $1.25/M = ~$0.02/request Not huge—but multiplied across sessions, it\u0026rsquo;s real money. And this assumes your cache stays valid (5 min TTL on most providers).\nHow This Changes Our Workflow # Here\u0026rsquo;s the shift in thinking:\nBefore: \u0026ldquo;Enable all MCP servers, AI will use what it needs.\u0026rdquo;\nAfter: \u0026ldquo;Enable MCP servers on-demand. AI will ask for what it needs.\u0026rdquo;\nHaving MCP ready isn\u0026rsquo;t about loading everything upfront. It\u0026rsquo;s about making the capability available through a smart gateway that only loads tool schemas when actually invoked. Or\u0026hellip; remember to enable and disable them when not needed.\nOther Proxies Exist—Why Build Another? # There are other MCP proxy solutions, but each has trade-offs:\ndynamic-mcp: Similar token optimization approach—it exposes only 2 tools initially (get_dynamic_tools, call_dynamic_tool) and loads the rest on-demand. It\u0026rsquo;s a Rust implementation, supports OAuth, and focuses on the same goal. Not that much different from LeanProxy, but when I tested I wasn\u0026rsquo;t able to get it working properly with the MCP I had. (I might have to try again)\nmcp-proxy: TypeScript proxy for converting stdio to HTTP/SSE. Useful for transport bridging but has no token optimization—it passes all tool schemas through.\nLiteLLM\u0026rsquo;s dynamic-mcp_route: Part of the LiteLLM proxy. Known to have SSE buffering issues, not ideal for streaming tool responses. And is quite big for only a simple MCP proxy to use locally (not intended for this local use case)\nLeanProxy is purpose-built for the specific problem: minimize token overhead while supporting stdio, HTTP, and SSE transports—with a focus on CLI-first workflows.\nLeanProxy: Performance Focus # Built in Go for performance, not just Python/Node convenience:\n# Startup is instant time leanproxy-mcp server run --stdio \u0026#34;npx -y @modelcontextprotocol/server-filesystem ./my-project\u0026#34; # Real-world: \u0026lt;50ms cold start # Dry-run for token savings reports leanproxy-mcp compactor --manifest ./mcp.json # Centralized server management leanproxy-mcp server list No heavy runtime dependencies. No npm install. Just a single binary.\nReal Examples # Before: Native MCP # $ leanproxy-mcp server list # Shows all 4 servers configured, but with full tool schemas # in every prompt NAME STATUS TRANSPORT COMMAND -------------------------------------------------------------- garmin enabled stdio uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp Intervals.icu enabled stdio /opt/homebrew/bin/uv run --with mcp[cli] --with-editable /opt/intervals-mcp-server mcp run /opt/intervals-mcp-server/src/intervals_mcp_server/server.py stitch enabled http https://stitch.googleapis.com/mcp github enabled stdio docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server 4 server(s) After: With LeanProxy # $ leanproxy-mcp server run --stdio \u0026#34;npx -y @modelcontextprotocol/server-filesystem ./my-project\u0026#34; # Router schema only: ~110 tokens # First tool invocation (e.g., garmin_get_stats): # → Schema loads JIT: ~500 tokens # → Subsequent prompts: cached See Token Savings # $ leanproxy-mcp compactor --manifest ~/.config/opencode/opencode.json Token Report: - Native MCP: 16,300 tokens/request - LeanProxy: ~2,000 tokens/request - Savings: 87% Why This Matters Now # The AI API market in 2026 is pay-per-use. A typical developer doing 20-30 sessions/day with 4 MCP servers enabled is burning:\nAt 16,300 tokens/session × 30 sessions × $0.04/1K = ~$19.56/day\nAt 2,000 tokens/session × 30 sessions × $0.04/1K = ~$2.40/day\nMonthly difference: ~$515/month just on MCP overhead.\nGet LeanProxy # Available on GitHub: https://github.com/mmornati/leanproxy-mcp\nRelated research: Read the earlier MCP vs CLI comparison for single-server lab data. This post extends it with real production measurements.\nInstall:\nbrew tap mmornati/leanproxy-mcp brew install leanproxy-mcp # Or download from releases curl -fsSL https://github.com/mmornati/leanproxy-mcp/releases/latest/download/... What\u0026rsquo;s Next? # Enable your MCP servers smartly. Keep the capability, lose the tax.\nThe future isn\u0026rsquo;t about having less. It\u0026rsquo;s about using what you need, when you need it.\n","date":"5 mai 2026","externalUrl":null,"permalink":"/the-hidden-tax-on-every-ai-request-how-mcp-servers-are-draining-your-token-budget/","section":"Posts","summary":"","title":"The Hidden Tax on Every AI Request: How MCP Servers Are Draining Your Token Budget","type":"posts"},{"content":"","date":"27 avril 2026","externalUrl":null,"permalink":"/tags/ai-agents/","section":"Tags","summary":"","title":"Ai-Agents","type":"tags"},{"content":"","date":"27 avril 2026","externalUrl":null,"permalink":"/tags/cli/","section":"Tags","summary":"","title":"Cli","type":"tags"},{"content":"","date":"27 avril 2026","externalUrl":null,"permalink":"/tags/developers-tools/","section":"Tags","summary":"","title":"Developers-Tools","type":"tags"},{"content":"","date":"27 avril 2026","externalUrl":null,"permalink":"/tags/github/","section":"Tags","summary":"","title":"Github","type":"tags"},{"content":"As Large Language Models (LLMs) evolve into autonomous coding agents, one of the most consequential architectural decisions is deceptively simple: how should an AI agent talk to external services?\nTraditionally, we gave LLMs terminal access and let them invoke Command Line Interfaces (CLIs). But in late 2024, Anthropic introduced the Model Context Protocol (MCP), marketed as the \u0026ldquo;USB-C of AI\u0026rdquo;, a structured alternative that lets agents interact with services via typed JSON schemas rather than shell commands and plain text output. The hype was immediate and enormous. Thousands of MCP servers were published within weeks, and every AI assistant rushed to add support.\nThe MCP Backlash: Why Developers Are Questioning the Hype # But in 2025, a quiet counter-current began to emerge. Developers building real agentic systems started noticing something uncomfortable: the more MCP servers they connected, the slower, dumber, and more expensive their agents became.\nThe core complaint is what engineers have started calling \u0026ldquo;context window bloat\u0026rdquo;. Unlike CLI tools, which an LLM can explore lazily via --help, MCP requires all registered tool schemas to be injected into the system prompt upfront. A single GitHub MCP server with ~35 endpoints contributes roughly 3,000 tokens of tool definitions to every single request, before the agent writes a single line. Connect five MCP servers (GitHub, Slack, Kubernetes, Linear, Postgres) and you\u0026rsquo;re burning 15,000+ tokens per request just describing tools the agent may never call. At scale, this can consume 25–50% of the entire context window before the agent begins reasoning.\nResearchers at lunar.dev documented another failure mode: tool-space interference. As tool counts rise, agents struggle to distinguish between similarly named tools (e.g., get_status, fetch_status, query_status), causing poor tool selection and cascading failures. Meanwhile, discussions on Reddit and communities like The New Stack are increasingly questioning whether MCP\u0026rsquo;s architectural overhead is justified for local or single-service workflows.\nThe developer community has also noted that frontier models are already heavily trained on common CLI tools, git, gh, kubectl, curl, often knowing the right flags without any schema description at all. As chrlschn.dev observed: \u0026ldquo;Progressive disclosure via --help might actually be more token-efficient than loading a 3,000-token schema you only use once.\u0026rdquo;\nSo: is the MCP backlash justified? Or are developers throwing the baby out with the bathwater? We ran a real experiment to find out, testing identical GitHub operations across four distinct approaches with measured token data.\nThe Experiment # We tested four distinct modalities for completing identical GitHub operations:\nID Approach Description A gh CLI (raw) Shell commands, plain-text output A2 gh CLI + Skill Shell commands guided by a skill.md file B Native GitHub MCP Directly injected JSON tool schemas C Nexus-Dev Gateway Single routing tool, schemas loaded lazily The Workflow # Each modality performed the same four operations:\nCreate a new public repository\nCreate an issue: \u0026ldquo;Test Issue for Evaluation\u0026rdquo;\nPost a comment on the issue\nList and retrieve the open issues\nAll four phases succeeded without errors. But looking at the token consumption tells a very different story.\nToken Consumption: The Real Numbers # Measurement method: Characters divided by 4, matching the cl100k_base tokenizer approximation used by most frontier LLMs.\nPer-Interaction Tokens (the 4 operations above) # Modality Input Tokens Output Tokens Total CLI (raw) 74 150 224 CLI + Skill 95 149 244 Native MCP 86 121 207 Nexus Gateway 135 111 246 Fixed Context Overhead (loaded once per session) # Modality Schema Overhead CLI (raw) 0 tokens — no upfront schema CLI + Skill 480 tokens — skill file loaded once Native MCP ~3,062 tokens — all 35 tool schemas always present Nexus Gateway ~20 tokens — single router schema Total Cost Formula # For a session with N operations:\nModality Formula N=10 N=50 N=200 CLI (raw) 224N 2,240 11,200 44,800 CLI + Skill 480 + 244N 2,920 12,680 49,280 Native MCP 3,062 + 207N 5,132 13,412 44,462 Nexus Gateway 20 + 246N 2,480 12,320 49,220 The Skill File: A Middle Ground # Before MCP was widely adopted, teams developed skill files, structured markdown documents injected into the LLM\u0026rsquo;s context that document exact commands, flags, and output formats. Think of it as a mini-manual the agent reads before acting.\nThe full skill file used in this experiment is available as a public Gist: github-cli.skill.md\nWhat the GitHub CLI Skill Provides # # Skill: GitHub CLI (`gh`) Operations ## Issue Operations # Always prefer --json for structured output: gh issue list -R \u0026lt;owner\u0026gt;/\u0026lt;repo\u0026gt; --json number,title,body,state,comments gh issue create -R \u0026lt;owner\u0026gt;/\u0026lt;repo\u0026gt; --title \u0026#34;\u0026lt;title\u0026gt;\u0026#34; --body \u0026#34;\u0026lt;body\u0026gt;\u0026#34; gh issue comment \u0026lt;issue-number\u0026gt; -R \u0026lt;owner\u0026gt;/\u0026lt;repo\u0026gt; --body \u0026#34;\u0026lt;comment\u0026gt;\u0026#34; Impact on Output Quality # Without the skill (gh issue list), the LLM receives:\nShowing 1 of 1 open issue in mmornati/mcp-cli-test-repo ID TITLE LABELS UPDATED #1 Test Issue for Evaluation less than a minute ago → ASCII table with alignment whitespace. No machine-readable structure. Dates are relative (\u0026ldquo;less than a minute ago\u0026rdquo;), not parseable timestamps. Plus upgrade noise.\nWith the skill (gh issue list --json number,title,body,state,comments):\n[{\u0026#34;body\u0026#34;:\u0026#34;This is a test issue created via CLI with Skill\u0026#34;,\u0026#34;comments\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;IC_kwD...\u0026#34;,\u0026#34;body\u0026#34;:\u0026#34;This is a comment via CLI with Skill\u0026#34;,\u0026#34;createdAt\u0026#34;:\u0026#34;2026-04-27T19:04:12Z\u0026#34;}],\u0026#34;number\u0026#34;:1,\u0026#34;state\u0026#34;:\u0026#34;OPEN\u0026#34;,\u0026#34;title\u0026#34;:\u0026#34;Test Issue for Evaluation\u0026#34;}] → Structured, parseable, no noise. Absolute timestamps. Machine-readable IDs.\nBreak-Even Analysis # The skill file costs 480 tokens upfront. In exchange, per-operation output quality improves dramatically.\nCLI vs. CLI+Skill cross-over: The skill overhead is recovered after ~24 operations within a session, after which output token reduction compounds.\nThe real gain from the skill isn\u0026rsquo;t just tokens, it\u0026rsquo;s eliminating the discovery loop where the LLM has to run gh --help or gh issue --help to find flags. Each help invocation typically costs an additional 400–800 tokens of output to parse.\nMCP: Structured by Design # Native MCP (Direct Schema Injection) # With the GitHub MCP server, the LLM doesn\u0026rsquo;t need to discover anything. Every tool is pre-described in the system prompt:\n// Input — compact and typed: {\u0026#34;body\u0026#34;: \u0026#34;This is a test issue\u0026#34;, \u0026#34;method\u0026#34;: \u0026#34;create\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;mmornati\u0026#34;, \u0026#34;repo\u0026#34;: \u0026#34;mcp-native-test-repo\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Test Issue for Evaluation\u0026#34;} // Output — clean JSON, no table formatting: {\u0026#34;id\u0026#34;: \u0026#34;4338179062\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://github.com/mmornati/mcp-native-test-repo/issues/1\u0026#34;} The per-interaction token cost is the lowest of all four modalities (207 tokens). However, the 3,062 token fixed overhead is significant, and it\u0026rsquo;s always there, even when the agent isn\u0026rsquo;t using GitHub at all.\nIf your AI assistant has 5 MCP servers active (GitHub, Slack, Kubernetes, Linear, Postgres), you\u0026rsquo;re paying 15,000+ tokens per request just to describe tools the agent may never call. At scale with long-running sessions, this quickly becomes the dominant cost.\nNexus-Dev Gateway (Lazy Schema Loading) # The Nexus-Dev gateway approach is architecturally elegant: inject a single routing tool (invoke_tool) with ~20 tokens of schema overhead, and let the agent request tool schemas on demand when it needs them.\n// The agent dispatches to any server with one tool: {\u0026#34;server\u0026#34;: \u0026#34;github\u0026#34;, \u0026#34;tool\u0026#34;: \u0026#34;issue_write\u0026#34;, \u0026#34;arguments\u0026#34;: {\u0026#34;owner\u0026#34;: \u0026#34;mmornati\u0026#34;, ...}} The per-operation tokens are slightly higher (246) because each call includes the routing envelope, but the fixed overhead is essentially zero regardless of how many backend servers are configured.\nThe Real Dev Session: A Completely Different Picture # All the numbers above measure the cost of calling GitHub. But that\u0026rsquo;s not how real coding sessions work.\nWhen a developer opens Claude Code, Cursor, or Antigravity and starts a feature, the actual pattern looks like this:\nPrompt 1: \u0026ldquo;Fetch issue #42 and help me plan the implementation\u0026rdquo; → 1 GitHub op\nPrompts 2–19: Coding, debugging, refactoring, tests, code review questions → 0 GitHub ops\nPrompt 20: \u0026ldquo;Create a PR with my changes and link it to the issue\u0026rdquo; → 1 GitHub op\nIn this 20-prompt session, GitHub was called twice. But some of our modalities charge you for GitHub on every single prompt, whether you call it or not.\nThis is the critical question: when is the overhead paid?\nModality When is overhead charged? CLI (raw) Only when a gh command is actually run CLI + Skill (on-demand) Once, when the developer explicitly invokes it CLI + Skill (always-on, e.g. in .cursorrules) Every prompt Native GitHub MCP Every prompt (schemas always in system prompt) Nexus Gateway Every prompt, but only ~20 tokens Token Cost Across a Full Dev Session (G=2 GitHub ops) # The following table shows the real total token cost for a dev session where GitHub is called exactly twice (fetch issue + create PR), and the rest of the session is pure coding:\nSession length CLI (raw) CLI+Skill (on-demand) CLI+Skill (always-on) Native GitHub MCP Nexus Gateway N=5 prompts 448 968 2,888 15,724 592 N=10 prompts 448 968 5,288 31,034 692 N=20 prompts 448 968 10,088 61,654 892 N=50 prompts 448 968 24,488 153,514 1,492 N=100 prompts 448 968 48,488 306,614 2,492 Data generated by session_token_model.py. Token approximation: 1 token ≈ 4 chars.\nThe numbers are staggering. In a 50-prompt dev session with 2 GitHub operations:\nCLI (raw) costs 448 tokens total for GitHub, exactly what those 2 calls require.\nNexus Gateway costs 1,492 tokens, still negligible.\nNative GitHub MCP costs 153,514 tokens, of which 99.7% is wasted on schema descriptions the agent never needed for 48 of the 50 prompts.\nThe Schema Tax: How Bad Is It Really? # For a 20-prompt session with 2 GitHub operations, the Native MCP breakdown is:\nTokens % of total Schema overhead (3,062 × 20 prompts) 61,240 99.3% Actual GitHub work (2 ops × 207 tokens) 414 0.7% Total 61,654 For every 1 token of real GitHub work done, Native MCP charges you 148 tokens in schema tax.\nWhat If GitHub Is Used More Heavily? # For completeness, here is the same session (N=20 prompts) with different GitHub call frequencies:\nGitHub ops (G) CLI (raw) CLI+Skill (on-demand) Native GitHub MCP Nexus Gateway G=1 (fetch only) 224 724 61,447 646 G=2 (fetch + PR) 448 968 61,654 892 G=5 (active use) 1,120 1,700 62,275 1,630 G=10 (heavy use) 2,240 2,920 63,310 2,860 G=20 (every prompt) 4,480 5,360 65,380 5,320 Notice that increasing GitHub calls from G=1 to G=20 barely moves the Native MCP number (61,447 → 65,380) because the schema overhead dominates completely. The Nexus Gateway, by contrast, scales almost linearly with actual usage.\nImportant Note on Skill Files # When a skill file is stored in a project-level configuration (like Cursor\u0026rsquo;s .cursorrules, Claude Code\u0026rsquo;s CLAUDE.md, or Antigravity\u0026rsquo;s skill registry), it is injected into every prompt automatically, meaning it behaves like always-on, costing 480 tokens × N prompts. However, if the developer explicitly references the skill file only when performing GitHub operations (on-demand), it costs just 480 tokens once per session. The on-demand model is far more efficient but requires developer discipline.\nSynthesis: Which Approach to Use? # Decision Matrix # Criterion CLI CLI + Skill Native MCP Gateway MCP Setup complexity ✅ None ✅ Minimal ⚠️ Schema authoring ⚠️ Gateway config Per-op tokens ✅ Low ✅ Low ✅ Lowest ⚠️ Moderate Fixed overhead per prompt ✅ Zero ✅ Zero (on-demand) ❌ ~3,062 tokens ✅ ~20 tokens Session cost (N=20, G=2) ✅ 448 ✅ 968 ❌ 61,654 ✅ 892 Output reliability ❌ Brittle text ⚠️ Better w/ --json ✅ Typed JSON ✅ Typed JSON Multi-service scale ✅ Fine ✅ Fine ❌ Explodes context ✅ Scales linearly Discovery overhead ❌ High (--help loops) ✅ Eliminated ✅ Eliminated ✅ On-demand Best for G/N ratio \u0026lt; 5% 5–15% \u0026gt; 40% 5–40% Recommendations # Use raw CLI when:\nThe service has G/N \u0026lt; 5% (called rarely in a session).\nRunning one-off scripts in constrained environments.\nUse CLI + Skill (on-demand) when:\nThe service has G/N \u0026lt; 15% but you need reliable structured output.\nYou want zero overhead except when the service is actually invoked.\n⚠️ Do not put the skill file in .cursorrules or CLAUDE.md, that makes it always-on and costs 480 tokens × N prompts.\nUse Native MCP when:\nThe service has G/N \u0026gt; 40% (called on nearly every prompt).\nYou have fewer than 2–3 MCP servers loaded simultaneously.\nExamples: file system tools, memory/context stores, local databases in data-heavy sessions.\nUse a Gateway MCP when:\nThe agent uses many different services at varying frequencies.\nYou want MCP-quality structured outputs for medium-frequency services (G/N 5–40%).\nThis is the recommended default architecture for general-purpose coding agents.\nConfiguring a Token-Efficient Dev Environment # Given everything we\u0026rsquo;ve measured, here is a practical framework for setting up your AI coding environment, whether you\u0026rsquo;re using Claude Code, Cursor, Antigravity, or any similar agent.\nThe Core Decision: G/N Ratio # For any external service, ask: \u0026ldquo;In a typical session of N prompts, how many prompts (G) will actually call this service?\u0026rdquo;\nG/N Ratio Interpretation Recommended Approach \u0026gt; 40% Core to almost every prompt Native MCP (overhead amortizes quickly) 15–40% Used regularly but not constantly Gateway MCP 5–15% Occasional use Gateway MCP or CLI+Skill (on-demand) \u0026lt; 5% Rare, session-bookend use CLI or on-demand skill For context: GitHub in a standard feature implementation session has G/N ≈ 10% (2 ops in 20 prompts). That puts it firmly in the \u0026ldquo;occasional use\u0026rdquo; zone, which is why Native MCP is such a poor fit despite its per-op efficiency.\nServices by Usage Frequency # Not all services are equal. Here\u0026rsquo;s how common MCP-compatible services break down by typical G/N ratio:\n🟢 High Frequency (G/N \u0026gt; 40%) → Native MCP is justified # Service Why it’s high-frequency CLI Alternative File system / code search Called on nearly every coding prompt find, grep, cat Memory / context stores (e.g. Nexus) Constantly queried for project context — Browser / web rendering Frequent in front-end sessions curl (limited) Local database Core when session is data-focused psql, sqlite3 Code index / embeddings Queried for every \u0026ldquo;find similar\u0026rdquo; request — For these, the schema overhead amortizes quickly and Native MCP\u0026rsquo;s structured outputs provide real value on every prompt.\n🟡 Medium Frequency (G/N 5–40%) → Gateway MCP # Service Typical ops/session Best approach Linear / Jira Fetch board + update tickets ~5–10 ops Slack / Teams Check thread, post update ~2–5 ops Notion / Confluence Look up docs, update notes ~2–5 ops Sentry / Datadog Investigate errors during debug ~3–8 ops npm / PyPI registry Check versions when adding deps ~2–6 ops For these, Native MCP schema overhead is hard to justify. A Gateway MCP gives you structured outputs without the fixed cost.\n🔴 Low Frequency (G/N \u0026lt; 5%) → CLI or on-demand skill # Service Typical use in a session Better approach GitHub Fetch issue at start, create PR at end CLI + on-demand skill Kubernetes / Helm Deploy once at the end of a feature kubectl + skill AWS / GCP / Azure Infra provisioning, rarely mid-session aws/gcloud CLI Stripe / payment APIs Verify test payments occasionally curl to API DNS / domain tools One-off lookups dig, nslookup For these, the Native MCP schema tax is almost entirely wasted. CLI with a skill file loaded on-demand costs zero overhead when idle and delivers clean structured JSON when explicitly invoked.\nPractical Configuration Guide # Step 1: Audit your MCP config. For each server, estimate G/N. A typical config that naively loads 5 servers wastes ~15,000 tokens per prompt:\n// BEFORE: 5 servers = ~15,000 tokens/prompt in schema overhead { \u0026#34;mcpServers\u0026#34;: { \u0026#34;github\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;@github/mcp-server\u0026#34;] }, \u0026#34;kubernetes\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;mcp-server-kubernetes\u0026#34;] }, \u0026#34;slack\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;@slack/mcp-server\u0026#34;] }, \u0026#34;aws\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;awslabs.aws-mcp-servers\u0026#34;] }, \u0026#34;linear\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;linear-mcp-server\u0026#34;] } } } Step 2: Apply the G/N framework:\n// AFTER: token-efficient configuration { \u0026#34;mcpServers\u0026#34;: { // ✅ Native MCP: G/N \u0026gt; 40%, core to every prompt \u0026#34;memory\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;@modelcontextprotocol/server-memory\u0026#34;] }, \u0026#34;filesystem\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;@modelcontextprotocol/server-filesystem\u0026#34;] }, // ✅ Gateway: routes to slack, linear, sentry on-demand; single ~20-token schema \u0026#34;nexus-gateway\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;nexus-dev-gateway\u0026#34;] } // ✅ github, kubernetes, aws → moved to CLI with on-demand skill files } } Step 3: Store skill files per-service, invoke on-demand:\nproject/ ├── .skills/ │ ├── github-cli.skill.md ← invoke when doing GitHub ops │ ├── kubernetes.skill.md ← invoke when deploying │ └── aws-cli.skill.md ← invoke when touching infra ├── CLAUDE.md ← only truly global rules here (keep minimal) ⚠️ Never put skill files in .cursorrules or CLAUDE.md unless you want them loaded on every prompt. Always-on skill files behave like miniature MCP schemas, costing 480 tokens × N prompts across your session.\nOther Services Worth Testing # The session-frequency framework applies consistently across the ecosystem:\nService CLI MCP Server Typical G/N Recommendation GitHub gh github-mcp-server ~5–10% CLI + on-demand skill Kubernetes kubectl kubernetes-mcp-server ~2–5% CLI + on-demand skill PostgreSQL psql postgres-mcp-server ~40–80% (data sessions) Native MCP if data-heavy Slack curl + API slack-mcp-server ~10–20% Gateway MCP Linear / Jira curl + API linear-mcp-server ~15–25% Gateway MCP AWS aws CLI awslabs aws-mcp ~2–5% CLI + on-demand skill Sentry curl + API sentry-mcp-server ~10–30% Gateway MCP Filesystem / Memory find, cat mcp-server-memory ~60–90% Native MCP The right answer depends on your session type. A PostgreSQL-heavy data analysis session has a completely different G/N profile than a feature implementation session where you only touch the database at the end for a schema migration.\nThe Happy Path Assumption: What These Numbers Don\u0026rsquo;t Include # Everything measured so far assumes that the LLM always picks the right tool, uses the correct flags, and generates valid parameters on the first attempt. In reality, it does not.\nThis is an important caveat. Our token numbers are lower bounds, they represent the ideal case. Real-world agentic sessions are messier.\nWhat Research Says About LLM Tool Accuracy # The Berkeley Function Calling Leaderboard (BFCL) is the most widely cited benchmark for tool-use accuracy. Key findings from 2024–2025:\nSimple, single-turn function calls: Top models (GPT-4o, Claude 3.5/4, Gemini) achieve \u0026gt;90% accuracy in well-defined, isolated scenarios.\nComplex, multi-turn, agentic tasks: Accuracy drops significantly, BFCL v4 (2025–2026) shows an average of ~58% across all models, with top models scoring ~73% on the comprehensive test suite.\nRelevance detection (knowing when not to call a tool) remains a consistent weak point.\nThese numbers don\u0026rsquo;t directly map to our scenarios, but they establish an important baseline: even top models fail to pick or call the right tool correctly 10–40% of the time in complex, multi-tool environments.\nWhat Failure Costs in Tokens # When an agent picks the wrong tool or uses wrong parameters, the typical failure cycle looks like:\n1. Agent generates wrong tool call / flag → ~100 tokens output 2. Tool returns error message → ~50–200 tokens input 3. Agent reasons about the error and retries → ~150 tokens output 4. (Repeat 1–3 until correct or max retries) A single recoverable error costs approximately 300–600 extra tokens. Research on ReAct-style agents shows that in some pipelines, over 90% of retry attempts target errors that are structurally impossible to fix (hallucinated tool names, invalid parameters), meaning the agent burns tokens on loops that can only end in a human intervention or hard reset.\nHow This Affects Each Modality Differently # Modality Primary failure mode Retry overhead Why CLI (raw) Hallucinated flags, wrong subcommand High Text-only interface; agent infers syntax from training data alone CLI + Skill Wrong flag despite skill guidance Medium Skill pre-empts most common mistakes; some edge cases remain Native MCP Wrong tool selection from large schema Medium-Low Typed schema prevents parameterization errors; tool confusion risk grows with schema size Nexus Gateway Misrouted request Low Single router with clear semantic labels; schema enforced downstream The counterintuitive finding from research: Native MCP actually has lower parameterization error rates than raw CLI for the same service, because the LLM receives a precise, typed function signature instead of inferring flags from documentation. The schema overhead is costly, but it does reduce one class of errors.\nEstimating Real-World Overhead # Without precise empirical data for our specific workflows, we can estimate the error-adjusted token cost using a conservative assumption: in a real coding session, the agent makes ~1 recoverable error per 5 GitHub-related operations (a 20% error rate, consistent with mid-range BFCL performance on agentic tasks).\nWith G=2 GitHub operations per session, this rounds to approximately one extra error/retry cycle per 2–3 sessions, so the per-session error overhead is small but non-zero:\nModality Ideal session cost (N=20, G=2) Error-adjusted estimate (+1 retry per session @ 400t) CLI (raw) 448 848 (+89%) CLI + Skill (on-demand) 968 1,168 (+21%) Native MCP 61,654 61,954 (+0.5%) Nexus Gateway 892 1,092 (+22%) Key observation: Error overhead hits CLI (raw) hardest in relative terms (+89%), because there is no schema to catch bad parameters before execution. For Native MCP, the error adjustment is statistically invisible (0.5%), because the schema tax already dominates by orders of magnitude.\n⚠️ Methodology note: All token numbers in this post represent the happy-path, single-attempt scenario. The error-adjusted estimates above are extrapolated from BFCL benchmark data and general research on ReAct-style agents. They are approximations, not measured values. Real error rates vary significantly based on model, prompt quality, schema clarity, and task ambiguity. Your mileage will vary.\nConclusion # Our experiment with real GitHub operations, creating repos, opening issues, posting comments, and querying results, confirms that the MCP backlash is partially right, but misses the real solution.\nThe Numbers That Matter Most # The isolated per-operation cost tells one story:\nModality Per-op tokens Session overhead CLI (raw) 224 0 CLI + Skill (on-demand) 244 480 (once) Native GitHub MCP 207 3,062 per prompt Nexus Gateway 246 20 per prompt But the real dev session cost (N=20 prompts, G=2 GitHub ops, fetch issue + create PR) tells the story that actually matters:\nModality Real session cost vs. CLI baseline CLI (raw) 448 tokens — CLI + Skill (on-demand) 968 tokens 2.2× Nexus Gateway 892 tokens 2.0× CLI + Skill (always-on) 10,088 tokens 22× Native GitHub MCP 61,654 tokens 137× At N=50 prompts, Native MCP reaches 153,514 tokens for the same 2 GitHub calls, with 99.7% wasted on schema descriptions that were never needed.\nThree Conclusions # 1. The MCP backlash is real, but the target is wrong. The problem isn\u0026rsquo;t the protocol. It\u0026rsquo;s the native injection pattern, loading every schema into every prompt. Returning to raw CLI trades one set of problems (schema bloat) for another (brittle text output, --help discovery loops).\n2. Service frequency (G/N ratio) is the missing variable in every MCP vs. CLI debate. GitHub has G/N ≈ 5–10% in a typical dev session, it belongs in the CLI+skill zone. File system tools and memory stores have G/N \u0026gt; 60%, they belong in the Native MCP zone. Design your toolchain around your actual usage patterns, not the hype cycle.\n3. The gateway pattern is the right default architecture. Near-zero fixed overhead (~20 tokens/prompt), MCP-quality structured outputs, and linear scaling regardless of how many backend services you configure. Pair it with on-demand skill files for low-frequency CLI services, and keep Native MCP only for the tools your agent calls on nearly every prompt.\nThe question every team building AI agents should be asking is not \u0026ldquo;should we use MCP?\u0026rdquo; but: \u0026ldquo;What is the G/N ratio of this service in our sessions, and are we paying the schema tax unnecessarily?\u0026rdquo;\nAll tests were run using the GitHub CLI v2.89.0, the github-mcp-server, and the nexus-dev gateway on macOS. The CLI skill file is published as a public Gist. Token estimates use the 1 token ≈ 4 characters approximation (cl100k_base).\n","date":"27 avril 2026","externalUrl":null,"permalink":"/the-future-of-agentic-tooling-mcp-servers-vs-cli-a-data-driven-comparison/","section":"Posts","summary":"","title":"The Future of Agentic Tooling: MCP Servers vs. CLI  A Data-Driven Comparison","type":"posts"},{"content":"","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/tags/home-assistant/","section":"Tags","summary":"","title":"Home-Assistant","type":"tags"},{"content":"","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/","section":"Mornati Blog","summary":"","title":"Mornati Blog","type":"page"},{"content":"","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/tags/solar-energy/","section":"Tags","summary":"","title":"Solar-Energy","type":"tags"},{"content":"","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/tags/solar-panels/","section":"Tags","summary":"","title":"Solar-Panels","type":"tags"},{"content":"","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"En ce début d\u0026rsquo;année 2026, la question pour tout propriétaire de panneaux solaires a évolué. Il ne s\u0026rsquo;agit plus seulement de savoir combien de panneaux on peut installer sur son toit, mais quelle quantité de cette énergie on peut réellement conserver pour soi. Avec des prix de l\u0026rsquo;électricité en France qui continuent de grimper, j\u0026rsquo;ai décidé d\u0026rsquo;analyser les chiffres de ma propre installation de 6 kWc pour voir si une batterie domestique est enfin un investissement rationnel.\nVivant dans le Nord de la France, le défi pour moi est double : un ensoleillement plus faible que dans le sud, et un chauffage hivernal (via pompe à chaleur) très énergivore.\nDans cet article, je partage les résultats d\u0026rsquo;une analyse de 1,5 an de production et je détaille la simulation réalisée en intégrant les données réelles de ma dernière facture EDF OA : le manque à gagner sur la revente.\nLe paysage énergétique en 2026 : Le poids du contrat # Les tarifs de revente ont beaucoup évolué. Pour mon installation, les conditions sont excellentes, ce qui change paradoxalement la rentabilité d\u0026rsquo;une batterie :\nCoût d\u0026rsquo;achat (Réseau) : ~0,208 €/kWh (HP) / ~0,164 €/kWh (HC)\nTarif de Revente (Mon contrat) : 13,01 c€/kWh (jusqu\u0026rsquo;à un plafond de 9 600 kWh/an).\nTarif de Revente (Nouveaux contrats 2026) : 4,00 c€/kWh.\nLe calcul de rentabilité repose sur l\u0026rsquo;économie nette : si je stocke 1 kWh pour ne pas l\u0026rsquo;acheter 0,20 €, mais que j\u0026rsquo;aurais pu le vendre 0,13 €, mon gain réel n\u0026rsquo;est que de 0,07 €.\nPourquoi les moyennes journalières sont trompeuses # Beaucoup de simulateurs en ligne utilisent des \u0026ldquo;moyennes quotidiennes\u0026rdquo;. C\u0026rsquo;est une erreur. Pour comprendre le ROI d\u0026rsquo;une batterie, il faut des données horaires. Une moyenne peut indiquer que vous avez produit 15 kWh et consommé 15 kWh, mais si la production est à midi et la consommation à minuit, sans batterie, vous achetez 100 % de votre énergie nocturne.\nMon profil de consommation (Données réelles) # Pour comprendre l\u0026rsquo;intérêt d\u0026rsquo;une batterie, il faut d\u0026rsquo;abord isoler la consommation \u0026ldquo;talon\u0026rdquo; de la maison des gros postes de dépense énergétique. Dans mon cas, deux éléments dominent : la Pompe à Chaleur (PAC) en hiver et la recharge du Véhicule Électrique (VE).\nL\u0026rsquo;analyse de mes données sur une année complète révèle une disparité saisonnière massive, exacerbée par le chauffage :\nSaison Consommation Nuit (23h-7h) Consommation Jour (7h-23h) Total Quotidien Hiver (avec PAC) 18,7 kWh ~12,3 kWh ~31 kWh Été 7,9 kWh ~4,1 kWh ~12 kWh L\u0026rsquo;impact du Véhicule Électrique (VE) # C\u0026rsquo;est ici que les moyennes deviennent piégeuses. Sur l\u0026rsquo;année, j\u0026rsquo;ai effectué 132 sessions de recharge, consommant un total de 2 454 kWh. Une recharge typique peut monter jusqu\u0026rsquo;à 60 kWh en une seule nuit.\nSi l\u0026rsquo;on inclut ces recharges dans la moyenne nocturne, on obtient un chiffre de 17,1 kWh/nuit. Mais en réalité :\n70% des nuits (sans recharge VE), ma consommation est modérée. 30% des nuits, la consommation explose pour charger la voiture. Ce point est crucial : charger une voiture électrique la nuit se fait déjà au tarif Heures Creuses (0,1637 €). Utiliser une batterie pour charger un VE reviendrait à stocker de l\u0026rsquo;électricité (avec 10% de perte) pour l\u0026rsquo;utiliser au même tarif\u0026hellip; une opération financièrement nulle.\nLe constat global reste sans appel : dans le Nord, l\u0026rsquo;hiver est une opération blanche. Ma pompe à chaleur consomme tout. Par contre, en été, l\u0026rsquo;excédent est massif. Sur ma dernière facture, j\u0026rsquo;ai injecté 2 431 kWh sur le réseau, générant 316,27 € de revenus (hors prime).\nAnalyse du ROI : L\u0026rsquo;impact du tarif de revente # Voici la simulation comparative entre mon contrat actuel et un contrat signé aujourd\u0026rsquo;hui.\nScénario A : Mon contrat (Revente à 13,01 c€/kWh) # Ici, chaque kWh stocké est un kWh \u0026ldquo;perdu\u0026rdquo; pour la revente à un prix élevé.\nTaille Batterie Coût Total Économie Réseau Manque à gagner revente Économie Nette Amortissement 5 kWh 3 400 € 282 € 176 € 106 € 32 ans 10 kWh 4 800 € 564 € 352 € 212 € 23 ans 15 kWh 6 200 € 845 € 528 € 317 € 20 ans Verdict : Avec un tarif de revente aussi élevé (13,01 c€), la batterie n\u0026rsquo;est absolument pas rentable financièrement. Elle mettrait 20 ans à s\u0026rsquo;amortir, soit bien au-delà de sa durée de vie probable.\nScénario B : Nouvelle installation 2026 (Revente à 4,00 c€/kWh) # Pour un nouvel acquéreur, la faible rémunération du surplus change la donne.\nTaille Batterie Coût Total Économie Réseau Manque à gagner revente Économie Nette Amortissement 5 kWh 3 400 € 282 € 54 € 228 € 15 ans 10 kWh 4 800 € 564 € 109 € 455 € 10 ans 15 kWh 6 200 € 845 € 164 € 681 € 9 ans Verdict : Pour un nouveau contrat, la batterie de 10-15 kWh devient rentable en 9 à 10 ans, ce qui correspond à la durée de garantie des constructeurs.\nFocus Technique : Comment j\u0026rsquo;ai validé ces chiffres # J\u0026rsquo;ai construit un pipeline d\u0026rsquo;analyse pour \u0026ldquo;rejouer\u0026rdquo; les 1,5 dernières années en simulant l\u0026rsquo;ajout d\u0026rsquo;une batterie.\nStockage long terme : J\u0026rsquo;utilise VictoriaMetrics pour stocker les données de mon ECU APSystems et de mon Linky.\nExtraction : Un script Python (collect_data.py) récupère les données par \u0026ldquo;chunks\u0026rdquo; de 31 jours pour éviter les timeouts.\nSimulation : Le script battery_simulator.py calcule l\u0026rsquo;état de charge (SoC) heure par heure, en appliquant une efficacité de 90%.\n# Extrait de la logique de simulation for hour in data: net_power = production - consommation if net_power \u0026gt; 0: # On charge la batterie avec l\u0026#39;excédent (perte de revente à 13.01c) current_soc += min(net_power, capacity - current_soc) * 0.90 else: # On décharge pour éviter l\u0026#39;achat réseau à 20.8c current_soc -= min(abs(net_power), current_soc) Conclusion : Faut-il sauter le pas ? # L\u0026rsquo;analyse est sans appel :\nSi vous avez un ancien contrat (type 13 c€/kWh) : Financièrement, ne le faites pas. Vendre votre surplus est plus rentable que de l\u0026rsquo;utiliser via une batterie coûteuse.\nSi vous démarrez aujourd\u0026rsquo;hui (4 c€/kWh) : La batterie est indispensable pour maximiser votre investissement.\nL\u0026rsquo;aspect Prime : N\u0026rsquo;oubliez pas que l\u0026rsquo;autoconsommation avec vente de surplus donne droit à une prime (dans mon cas 1 380 €), ce qui aide à financer l\u0026rsquo;installation initiale, mais ne change pas la logique de cycle de la batterie.\nLe choix de la batterie en 2026 est donc devenu une question de contrat autant que de technologie.\nRetrouvez les scripts et les données sur mon GitHub : ha-energy-analysis.\n","date":"22 mars 2026","externalUrl":null,"permalink":"/fr/une-batterie-solaire-est-elle-rentable-en-2026/","section":"Posts","summary":"","title":"Une batterie solaire est-elle rentable en 2026 ?","type":"posts"},{"content":"","date":"14 mars 2026","externalUrl":null,"permalink":"/tags/ai-coding-agent/","section":"Tags","summary":"","title":"Ai-Coding-Agent","type":"tags"},{"content":"","date":"14 mars 2026","externalUrl":null,"permalink":"/tags/developer/","section":"Tags","summary":"","title":"Developer","type":"tags"},{"content":"","date":"14 mars 2026","externalUrl":null,"permalink":"/tags/development/","section":"Tags","summary":"","title":"Development","type":"tags"},{"content":"I’ve spent the better part of the last year \u0026ldquo;vibecoding\u0026rdquo; with AI. The coding results have been quite good, but recently, I’ve been looking to improve the top of the funnel: how to move smoothly from a raw idea to a strictly defined, \u0026ldquo;ready to dev\u0026rdquo; project that does exactly what I want.\nTo test this, I decided to dedicate a full day to really digging into the BMAD method (which stands for Breakthrough Method for Agile AI-Driven Development). If you aren\u0026rsquo;t familiar with it, BMAD is an open-source framework that basically applies Agile discipline to AI coding. Instead of just treating the AI as a single, chaotic autocomplete tool, BMAD forces you to interact with specialized AI \u0026ldquo;personas\u0026rdquo; (like an Analyst, Product Manager, and Architect). It makes you generate strict, version-controlled artifacts, like a PRD and technical architecture, before any actual code is written.\nI’d used it a few times before and found it a bit long, but this time, I gave it the time it deserved. The goal? Take a raw idea through every phase of software design using these AI agents, right up to the point of coding. Here is how it went, and more importantly, what it taught me about the future of our jobs.\nThe Journey from Idea to MVP # Using the BMAD method, I took my test idea through five distinct phases:\n1. Market Search: Let’s be honest, most of our \u0026ldquo;genius\u0026rdquo; ideas have already been built by someone else! The Analyst agent guides you through a complete market analysis, pulling in data and results. It acts as an early reality check to see if the project is actually worth pursuing.\n2. The Analyst (Building the PRD): This is where you build the Product Requirements Document, and it’s where I spent a lot of time. The method drives you down different paths, asking relentless questions and adapting to your answers. It was fascinating because my idea actually grew stronger throughout the process. Step by step, the AI helped me bolt on new features to improve the core product.\n3. Epics and Stories: Once the PRD is locked, the Product Manager agent steps in to define the epics and user stories for the MVP. The agent continues to guide and ask questions, but you have full control to adapt its proposals.\n4. The Tech Architect: Here is where rubber meets the road. You move from functional/non-functional requirements to technical ones. The Architect agent proposes stacks and architecture directions based on SLA requirements. You then drive it toward your preferred solution: identifying components, frameworks, specific versions, and deployment strategies.\n5. The UX: Since my application had a frontend, I entered the design phase. The agent generated Markdown files describing page styles and even spit out sample HTML pages to visualize the result.\nAt this point, you ask the agent for a \u0026ldquo;readiness check.\u0026rdquo; It reviews everything, fixes a few lingering issues, and boom: step one of your project is done.\nBreaking the \u0026ldquo;AI Aesthetic\u0026rdquo; with Stitch # There’s a frustrating reality in vibe coding: if you don’t give the AI highly specific prompts, it will default to the exact same theme style. All AI-generated apps start to look suspiciously similar.\nTo fix this, I spent some time using a different tool for the UX. Since I have a Google AI subscription, I jumped into Stitch. It was honestly quite impressive.\nI took the Markdown files generated in the previous step (describing the project and pages) and asked the LLM to draw the different pages. Stitch acts almost like an AI-empowered Figma. You can manually tweak text, positions, and images, or just ask the AI to modify them for you.\nA Quick Tooling Tip: For this entire discovery and design process, I strictly used gemini-cli (also part of my subscription). Because it uses a different quota, it allowed me to save all my tokens in antigravity purely for the heavy lifting of the actual development phase.\nOnce the design is done, the next steps are standard: feed the product info and architecture into the developer agents, ask them to generate highly-detailed, \u0026ldquo;AI-implementable\u0026rdquo; tech stories, and let the agents code and test them.\nSo\u0026hellip; Are Developers Being Replaced? # Going through this process made me think deeply about the current state of the \u0026ldquo;Developer.\u0026rdquo;\nRight now, anyone with a solid idea and enough domain knowledge to challenge an AI can do almost all of the first part of this process\u0026hellip; except for the technical architecture. When the AI proposes architecture, it asks technical questions to move forward. It asks for guidance on deployment, performance bottlenecks, data flows, and language choices. This is where technical skills are still absolutely required. Recent industry data heavily supports this shift. According to late-2025/early-2026 reports from firms like Gartner and DX:\nCode generation is mainstream, but it\u0026rsquo;s not the whole job: ~93% of developers now use AI coding assistants. Furthermore, around 27% of all production code is now entirely AI-authored.\nThe \u0026ldquo;10x Developer\u0026rdquo; myth is dead: While AI speeds up raw coding tasks by about 26% (saving devs ~3.6 hours a week), the overall organizational delivery speed has only improved by about 8-10%. Why? Because the bottleneck simply shifted from writing code to reviewing and architecting systems.\nThe 70% Problem: AI gets you 70% of the way there incredibly fast. But bridging that final 30%—fixing edge cases, ensuring security, and tying complex microservices together—requires deep human expertise. In fact, unmonitored AI code has been shown to introduce 1.7x more defects.\nI don\u0026rsquo;t necessarily want to call someone who doesn\u0026rsquo;t type code a \u0026ldquo;Developer\u0026rdquo; anymore. Maybe we are all evolving into \u0026ldquo;Software Engineers\u0026rdquo; in the truest sense of the word. Or perhaps we need a completely new job title: Agent Supervisor? (Gartner actually predicts that by 2028, the developer\u0026rsquo;s role will officially shift from implementation to orchestration—so we are already there!).\nThe Junior and PM Dilemmas # This evolution brings up two massive questions for the industry:\nWhat about Juniors? How does someone who doesn\u0026rsquo;t yet know architecture become an \u0026ldquo;expert\u0026rdquo; Agent Supervisor? Recent studies have shown a worrying trend: developers who use AI just to generate code for them (without understanding it) score significantly lower on comprehension tests. If they aren\u0026rsquo;t grinding out the code, how do they learn? The answer is the same as it has always been: reading, breaking things, and mentorship. People must work together and share experiences. AI doesn\u0026rsquo;t replace the senior-junior mentorship dynamic; it makes it more critical than ever.\nWhat about Non-Technical Roles? This is the harder pill to swallow. If a technical \u0026ldquo;Agent Supervisor\u0026rdquo; can use AI to do all the discovery, market research, and PRD analysis (like I did in a single day), where does that leave traditional Product Managers? Why shouldn\u0026rsquo;t technical people just own the product readiness phase and then immediately move on to the coding agents?\nThe landscape is shifting rapidly. Typing the code itself is becoming the easy part. The real value is now in the vision, the architecture, and the ability to confidently supervise the machine that builds it.\n","date":"14 mars 2026","externalUrl":null,"permalink":"/what-is-a-developer-when-we-use-coding-agents-my-1-day-bmad-experiment/","section":"Posts","summary":"","title":"What is a Developer When We Use Coding Agents? My 1-Day BMAD Experiment","type":"posts"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/api/","section":"Tags","summary":"","title":"Api","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/claude/","section":"Tags","summary":"","title":"Claude","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/csnet/","section":"Tags","summary":"","title":"Csnet","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/heat-pump/","section":"Tags","summary":"","title":"Heat-Pump","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/hitachi/","section":"Tags","summary":"","title":"Hitachi","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/iot/","section":"Tags","summary":"","title":"Iot","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/reverse-engineering/","section":"Tags","summary":"","title":"Reverse-Engineering","type":"tags"},{"content":"When Hitachi replaced its older Hi-Kumo system with the ATW-IOT-01 module, it broke every existing Home Assistant integration for their heat pumps. The new system routes everything through a cloud service called CSNet Manager — and there\u0026rsquo;s no public API, no documentation, no SDK. Just a web app.\nI decided to reverse-engineer it and build a complete Home Assistant integration from scratch. Not by spending weeks manually reading JavaScript and mapping HTTP calls, but by using AI as my primary tool. Here\u0026rsquo;s how I did it — and how you can apply the same approach to any undocumented web service.\nStep 1: Inspecting the Web Application # The first thing I did was open the CSNet Manager website and fire up the browser\u0026rsquo;s DevTools. The Network tab is your best friend when reverse-engineering any web application.\nAfter logging in, the dashboard shows a clean interface with your heating zones — in my case, two zones (\u0026ldquo;Bibliothèque\u0026rdquo; and \u0026ldquo;Salon\u0026rdquo;) with their target and current temperatures:\nBut the real gold is in what happens behind the scenes. Filtering by XHR/Fetch requests in the Network tab, I quickly found that the web app calls several REST endpoints, all returning JSON:\nEndpoint\nPurpose\n/login\nAuthentication with XSRF token\n/data/elements\nThe main one — temperatures, modes, alarms, for all zones\n/data/installationdevices\nDevice details, heating status, settings, temperature limits\n/data/installationalarms\nActive and historical alarm data\n/data/indoor/heat_setting\nPOST endpoint to change settings (temperature, mode, etc.)\n/data/rooms\nRoom configuration\n/data/installations\nInstallation metadata\n/data/user\nUser profile data\nNavigating directly to /data/elements, I could see the raw JSON response:\n💡 Tip: If you\u0026rsquo;re reverse-engineering a web service, start with the Network tab. If the API returns JSON (and not some proprietary binary format), you\u0026rsquo;re in luck — the backporting work will be much simpler.\nThis was the first \u0026ldquo;good news\u0026rdquo;: the API is straightforward HTTP calls with JSON responses. No WebSockets, no GraphQL, no obfuscated binary protocol. Just good old REST.\nStep 2: Understanding the Data — The Hard Part # Here\u0026rsquo;s a sample of what the /data/elements response looks like (redacted):\n{ \u0026#34;status\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;data\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;Maison de Marco\u0026#34;, \u0026#34;weatherTemperature\u0026#34;: 11, \u0026#34;elements\u0026#34;: [ { \u0026#34;deviceName\u0026#34;: \u0026#34;Hitachi PAC\u0026#34;, \u0026#34;parentName\u0026#34;: \u0026#34;Salon\u0026#34;, \u0026#34;elementType\u0026#34;: 1, \u0026#34;mode\u0026#34;: 1, \u0026#34;realMode\u0026#34;: 1, \u0026#34;onOff\u0026#34;: 1, \u0026#34;operationStatus\u0026#34;: 5, \u0026#34;settingTemperature\u0026#34;: 18.5, \u0026#34;currentTemperature\u0026#34;: 23.0, \u0026#34;ecocomfort\u0026#34;: 1, \u0026#34;alarmCode\u0026#34;: 0, \u0026#34;c1Demand\u0026#34;: false, \u0026#34;c2Demand\u0026#34;: true, \u0026#34;silentMode\u0026#34;: -1, \u0026#34;fanSpeed\u0026#34;: -1, \u0026#34;doingBoost\u0026#34;: false, \u0026#34;yutaki\u0026#34;: true } ] } } The field names are somewhat descriptive, but what do the values mean? What is elementType: 1 vs elementType: 5? What\u0026rsquo;s operationStatus: 5? What does ecocomfort: 1 map to?\nAnd here\u0026rsquo;s the real challenge: I only have one device with one specific configuration — two water circuits, no water heater, no swimming pool, no fan coils. To make this integration useful for everyone, I needed to support configurations I don\u0026rsquo;t have. How do you understand data you\u0026rsquo;ve never seen?\nStep 3: Reading the JavaScript Source — Bingo # The answer was staring at me from the browser\u0026rsquo;s Sources tab. The JavaScript files powering the CSNet Manager web app contain all the logic to interpret the API data. And fortunately, they\u0026rsquo;re not heavily obfuscated.\nIn a file like csnet.js, I found exactly what I needed:\nOperation status codes:\n// From the CSNet Manager JavaScript source var OPST_OFF = 0; var OPST_COOL_D_OFF = 1; var OPST_COOL_T_OFF = 2; var OPST_COOL_T_ON = 3; var OPST_HEAT_D_OFF = 4; var OPST_HEAT_T_OFF = 5; // ← My \u0026#34;Salon\u0026#34; has this value! var OPST_HEAT_T_ON = 6; var OPST_DHW_OFF = 7; var OPST_DHW_ON = 8; var OPST_SWP_OFF = 9; var OPST_SWP_ON = 10; var OPST_ALARM = 11; Temperature limit validation:\nfunction validateValue(v, def) { if (v != null \u0026amp;\u0026amp; v != undefined \u0026amp;\u0026amp; v != 0 \u0026amp;\u0026amp; v != -1) return v; return def; } Element type mapping:\nelementType 1 = C1 Air circuit (standard heat pump, 8-35°C range)\nelementType 2 = C2 Air circuit\nelementType 3 = DHW (Domestic Hot Water)\nelementType 4 = SWP (Swimming Pool)\nelementType 5 = C1 Water circuit (Yutaki/Hydro, 20-80°C range)\nelementType 6 = C2 Water circuit\nAlarm origin maps, fan speed constants, OTC (Outdoor Temperature Compensation) types — everything was there, clearly written in JavaScript, waiting to be translated into Python.\n💡 Key insight: When a web service has no API documentation, the JavaScript source code is the documentation. The browser needs to understand the data to display it, so the code is effectively a reference implementation.\nStep 4: Bringing in the AI # Now came the fun part. Instead of manually reading through thousands of lines of JavaScript and mapping every constant, every condition, every edge case into Python — I fed everything to an AI.\nThe Architect: Claude Opus # I used Claude Opus (available in tools like Antigravity and GitHub Copilot) as my architect. Here\u0026rsquo;s what I asked it to do:\nAnalyse the JavaScript source files — understand the data model, the constants, the business logic\nCross-reference with the JSON API responses — map every field to its meaning\nDesign the Home Assistant integration architecture — entities, sensors, coordinators, config flows\nCreate detailed GitHub issues — each one a user story with acceptance criteria, technical notes, and implementation details\nThe AI produced a structured breakdown organized into milestones:\nMilestone\nFocus\nExample Issues\nPhase 1\nCore HVAC Features\nClimate entities, temperature control, mode switching, dynamic temperature limits\nPhase 2\nSensors \u0026amp; Monitoring\nTemperature sensors, alarm monitoring, device status, operation status\nPhase 3\nAdvanced Features\nSilent mode, fan speed control, OTC monitoring, water heater, swimming pool\nEach issue looked something like:\n\\[Enhancement\\] Add Silent/Quiet Mode Support (#64)\nWhat: Add silent mode control based on the silentMode field from the elements API\nTechnical notes: The JavaScript uses silentMode: 0 for off and silentMode: 1 for on. The value -1 means the feature is not available for this device.\nAcceptance criteria:\nSwitch entity that toggles silent mode\nEntity is only created when silentMode ≠ -1\nToggle sends POST to /data/indoor/heat_setting with silentMode parameter\nYou can see all these issues on the GitHub issues page.\nStep 5: The AI-Driven Development Workflow # With the architecture defined and the issues created, I entered the implementation phase. Here\u0026rsquo;s the workflow I used consistently throughout the project:\nThe Architect + Coder Model # Why two models? Using a highly capable model (Claude Opus) for architecture and a faster/cheaper model (Claude Sonnet, GitHub Copilot) for implementation keeps costs reasonable while maintaining quality. The architect model produces detailed enough specifications that a less powerful model can implement them accurately.\nFor each issue:\nThe coding AI creates a feature branch\nIt implements the code following the issue specifications\nIt writes unit tests\nIt creates a PR with a description of all changes\nI review the code, test on my real Hitachi heat pump, and merge\nYou can see the entire history of this process in the pull requests — each PR is a single feature or bug fix, with a clear description of what was implemented and why.\nStep 6: Community-Driven Refinement — The Secret Weapon # Here\u0026rsquo;s where the project truly came alive. Building the initial integration was one thing — making it work for everyone was another.\nRemember my earlier challenge? I only have one device configuration (two air circuits, no water heater, no pool). How do you support hardware you don\u0026rsquo;t own?\nThe answer: the community.\nWithin weeks of releasing the first version on HACS, 4-5 users with different Hitachi configurations started regularly testing and reporting feedback. Each new tester was like finding a puzzle piece I couldn\u0026rsquo;t buy:\n🔥 One user had a DHW (Domestic Hot Water) heater → We discovered elementType: 3 and the settingTempDHW field, then built the water heater entity\n🏊 Another had a swimming pool heater → We found elementType: 4 with its 24-33°C temperature range\n🌡️ A user with a Yutaki S2 + Yutampo waterboiler → Confirmed the integration works with water circuits (elementType: 5 and 6)\n💨 Someone with fan coils reported a different speed mapping → We added legacy vs standard fan speed models\n📊 Users asked for more sensors — compressor stats, outdoor temperatures, pump speeds → We surfaced more data from the installationdevices endpoint\nEach time someone reported \u0026ldquo;I have this configuration and here\u0026rsquo;s my elements JSON\u0026rdquo;, I knew we could expand support. The JSON response from /data/elements became our common debugging language — any user could capture it from their browser and share it (redacting private data) to help identify unmapped fields.\nThe real \u0026ldquo;aha moment\u0026rdquo;: Every time someone said \u0026ldquo;I have this specific setup and I can test\u0026rdquo;, it felt like unlocking a new level. We could never have tested swimming pool or fan coil support without those volunteers.\nCommunity testers also helped catch subtle bugs: wrong temperature readings, incorrect operation status mapping, credential management issues.\nThe Result # Today, the Hitachi CSNet Home integration is a complete Home Assistant custom component with:\nClimate entities per zone with HVAC modes (heat/cool/off), presets (comfort/eco), and target temperature control\nWater heater entity with eco/performance modes\n40+ sensors for temperatures, operation status, alarm monitoring, compressor stats, and more\nAdvanced features: silent mode, fan speed control, OTC (Outdoor Temperature Compensation) monitoring\nAlarm system with persistent notifications and historical alarm tracking\nMulti-zone support for C1/C2 air and water circuits\nNumbers that tell the story:\nMetric\nValue\nClosed issues\n166+\nReleases\n29\nContributors\n11\nPython codebase\n~5,000 lines (integration + tests)\nCI/CD\n32+ test combinations across 7+ HA versions\nHACS\n✅ Available\nAI vs Manual: The Time Factor # Let me be honest about what AI did and didn\u0026rsquo;t do in this project.\nWhat AI excelled at: # Code translation (JS → Python): The AI could read JavaScript source code, understand the logic, and produce equivalent Python in minutes — work that would take hours manually\nPattern recognition: Mapping cryptic field names to meaningful constants across thousands of lines of JS\nBoilerplate generation: Home Assistant integration structure, config flows, entity platforms — all the scaffolding that takes time to write but follows clear patterns\nIssue decomposition: Breaking a complex project into well-structured, implementable user stories\nWhat AI couldn\u0026rsquo;t do: # Test with real hardware. Only real devices connected to the CSNet cloud can validate the integration works\nUnderstand edge cases from a single data point. AI could map elementType: 1 to \u0026ldquo;air circuit\u0026rdquo;, but it couldn\u0026rsquo;t know that elementType: 5 encodes temperature differently (multiplied by 10) without seeing the JS logic\nReplace community interaction. Understanding that some users have legacy fan coils with a different speed mapping required human conversation and debugging\nThe time comparison: # With AI: Core integration in 2-3 days. Full-featured with community refinement in a few weeks\nWithout AI (estimated): Core integration would take 2-3 weeks of reading JavaScript, understanding protocols, writing Python manually. Full-featured? Months.\nThe AI didn\u0026rsquo;t save 80% of the thinking — it saved 80% of the typing and translating. The human work remained essential: making architectural decisions, reviewing code, testing on real hardware, and working with the community.\nYour Turn: A Recipe for Reverse-Engineering Any Web Service # If you want to apply this approach to another undocumented web service, here\u0026rsquo;s the step-by-step:\n1. 🔍 Inspect the Network Traffic # Open DevTools → Network tab\nInteract with the web app and identify the API calls\nLook for JSON responses — that\u0026rsquo;s your best-case scenario\n2. 📖 Read the JavaScript Source # Check the Sources tab for unminified JS\nUse prettier or your IDE to format minified code\nLook for constants, enums, mapping functions\n3. 🤖 Feed Everything to an AI # Give the AI: JavaScript source + sample JSON responses + context about what the web app does\nAsk it to: map every field, identify the data model, create a technical specification\nUse a capable model (Claude Opus, GPT-5.x) for this architectural analysis\n4. 📋 Create Structured Issues # Ask the AI to produce detailed GitHub issues from the spec\nEach issue = one feature, with acceptance criteria and technical notes\nOrganize into milestones (core features first, then enhancements)\n5. 💻 Implement with AI Assistance # Use a coding AI (Claude Sonnet, Copilot) to implement each issue\nKeep PRs focused and small\nAlways review the code yourself — AI makes subtle mistakes\n6. 👥 Release Early, Get Community Feedback # Don\u0026rsquo;t wait for perfection\nUsers with different configurations will find things you never could\nMake it easy for them to share data (JSON responses, debug logs)\nFinal Thoughts # What I built here with AI could absolutely be done manually. The process of inspecting network calls, reading JavaScript, understanding data formats — it\u0026rsquo;s all standard reverse-engineering. Developers have been doing it for decades.\nBut the timeframe is completely different. What took days with AI would have taken weeks without it. The AI handled the tedious translation work — reading thousands of lines of JavaScript, mapping constants, generating boilerplate — while I focused on what humans do best: architecture, testing, and community collaboration.\nThe real magic wasn\u0026rsquo;t just the AI. It was the combination of AI-accelerated development and a community of 4-5 dedicated users, each with a unique Hitachi configuration, who gave regular feedback, tested features they needed, and helped the integration grow from a proof of concept into something that genuinely works for everyone.\nLinks:\n📦 Repository: github.com/mmornati/home-assistant-csnet-home\n📚 Documentation: mmornati.github.io/home-assistant-csnet-home\n💬 Discussions: GitHub Discussions\n🛒 HACS: Search for \u0026ldquo;csnet home\u0026rdquo; or \u0026ldquo;Hitachi\u0026rdquo;\nHave you reverse-engineered a web service with AI? Found a cloud-only IoT device that needed a local integration? I\u0026rsquo;d love to hear about your experience — leave a comment or open a discussion on the repo!\n","date":"25 février 2026","externalUrl":null,"permalink":"/reverse-engineering-hitachis-cloud-api-with-ai-from-browser-devtools-to-a-full-home-assistant-integration-1/","section":"Posts","summary":"","title":"Reverse-Engineering Hitachi's Cloud API with AI: From Browser DevTools to a Full Home Assistant Integration","type":"posts"},{"content":"","date":"25 février 2026","externalUrl":null,"permalink":"/tags/smart-home/","section":"Tags","summary":"","title":"Smart-Home","type":"tags"},{"content":" Reverse-Engineering Hitachi\u0026rsquo;s Cloud API with AI: From Browser DevTools to a Full Home Assistant Integration # When Hitachi replaced its older Hi-Kumo system with the ATW-IOT-01 module, it broke every existing Home Assistant integration for their heat pumps. The new system routes everything through a cloud service called CSNet Manager — and there\u0026rsquo;s no public API, no documentation, no SDK. Just a web app.\nI decided to reverse-engineer it and build a complete Home Assistant integration from scratch. Not by spending weeks manually reading JavaScript and mapping HTTP calls, but by using AI as my primary tool. Here\u0026rsquo;s how I did it — and how you can apply the same approach to any undocumented web service.\nStep 1: Inspecting the Web Application # The first thing I did was open the CSNet Manager website and fire up the browser\u0026rsquo;s DevTools. The Network tab is your best friend when reverse-engineering any web application.\nAfter logging in, the dashboard shows a clean interface with your heating zones — in my case, two zones (\u0026ldquo;Bibliothèque\u0026rdquo; and \u0026ldquo;Salon\u0026rdquo;) with their target and current temperatures:\nBut the real gold is in what happens behind the scenes. Filtering by XHR/Fetch requests in the Network tab, I quickly found that the web app calls several REST endpoints, all returning JSON:\nEndpoint Purpose /login Authentication with XSRF token /data/elements The main one — temperatures, modes, alarms, for all zones /data/installationdevices Device details, heating status, settings, temperature limits /data/installationalarms Active and historical alarm data /data/indoor/heat_setting POST endpoint to change settings (temperature, mode, etc.) /data/rooms Room configuration /data/installations Installation metadata /data/user User profile data Navigating directly to /data/elements, I could see the raw JSON response:\n💡 Tip: If you\u0026rsquo;re reverse-engineering a web service, start with the Network tab. If the API returns JSON (and not some proprietary binary format), you\u0026rsquo;re in luck — the backporting work will be much simpler.\nThis was the first \u0026ldquo;good news\u0026rdquo;: the API is straightforward HTTP calls with JSON responses. No WebSockets, no GraphQL, no obfuscated binary protocol. Just good old REST.\nStep 2: Understanding the Data — The Hard Part # Here\u0026rsquo;s a sample of what the /data/elements response looks like (redacted):\n{ \u0026#34;status\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;data\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;Maison de Marco\u0026#34;, \u0026#34;weatherTemperature\u0026#34;: 11, \u0026#34;elements\u0026#34;: [ { \u0026#34;deviceName\u0026#34;: \u0026#34;Hitachi PAC\u0026#34;, \u0026#34;parentName\u0026#34;: \u0026#34;Salon\u0026#34;, \u0026#34;elementType\u0026#34;: 1, \u0026#34;mode\u0026#34;: 1, \u0026#34;realMode\u0026#34;: 1, \u0026#34;onOff\u0026#34;: 1, \u0026#34;operationStatus\u0026#34;: 5, \u0026#34;settingTemperature\u0026#34;: 18.5, \u0026#34;currentTemperature\u0026#34;: 23.0, \u0026#34;ecocomfort\u0026#34;: 1, \u0026#34;alarmCode\u0026#34;: 0, \u0026#34;c1Demand\u0026#34;: false, \u0026#34;c2Demand\u0026#34;: true, \u0026#34;silentMode\u0026#34;: -1, \u0026#34;fanSpeed\u0026#34;: -1, \u0026#34;doingBoost\u0026#34;: false, \u0026#34;yutaki\u0026#34;: true } ] } } The field names are somewhat descriptive, but what do the values mean? What is elementType: 1 vs elementType: 5? What\u0026rsquo;s operationStatus: 5? What does ecocomfort: 1 map to?\nAnd here\u0026rsquo;s the real challenge: I only have one device with one specific configuration — two air circuits, no water heater, no swimming pool, no fan coils. To make this integration useful for everyone, I needed to support configurations I don\u0026rsquo;t have. How do you understand data you\u0026rsquo;ve never seen?\nStep 3: Reading the JavaScript Source — Bingo # The answer was staring at me from the browser\u0026rsquo;s Sources tab. The JavaScript files powering the CSNet Manager web app contain all the logic to interpret the API data. And fortunately, they\u0026rsquo;re not heavily obfuscated.\nIn a file like csnet.js, I found exactly what I needed:\nOperation status codes:\n// From the CSNet Manager JavaScript source var OPST_OFF = 0; var OPST_COOL_D_OFF = 1; var OPST_COOL_T_OFF = 2; var OPST_COOL_T_ON = 3; var OPST_HEAT_D_OFF = 4; var OPST_HEAT_T_OFF = 5; // ← My \u0026#34;Salon\u0026#34; has this value! var OPST_HEAT_T_ON = 6; var OPST_DHW_OFF = 7; var OPST_DHW_ON = 8; var OPST_SWP_OFF = 9; var OPST_SWP_ON = 10; var OPST_ALARM = 11; Temperature limit validation:\nfunction validateValue(v, def) { if (v != null \u0026amp;\u0026amp; v != undefined \u0026amp;\u0026amp; v != 0 \u0026amp;\u0026amp; v != -1) return v; return def; } Element type mapping:\nelementType 1 = C1 Air circuit (standard heat pump, 8-35°C range) elementType 2 = C2 Air circuit elementType 3 = DHW (Domestic Hot Water) elementType 4 = SWP (Swimming Pool) elementType 5 = C1 Water circuit (Yutaki/Hydro, 20-80°C range) elementType 6 = C2 Water circuit Alarm origin maps, fan speed constants, OTC (Outdoor Temperature Compensation) types — everything was there, clearly written in JavaScript, waiting to be translated into Python.\n💡 Key insight: When a web service has no API documentation, the JavaScript source code is the documentation. The browser needs to understand the data to display it, so the code is effectively a reference implementation.\nStep 4: Bringing in the AI # Now came the fun part. Instead of manually reading through thousands of lines of JavaScript and mapping every constant, every condition, every edge case into Python — I fed everything to an AI.\nThe Architect: Claude Opus # I used Claude Opus (available in tools like Antigravity and GitHub Copilot) as my architect. Here\u0026rsquo;s what I asked it to do:\nAnalyse the JavaScript source files — understand the data model, the constants, the business logic Cross-reference with the JSON API responses — map every field to its meaning Design the Home Assistant integration architecture — entities, sensors, coordinators, config flows Create detailed GitHub issues — each one a user story with acceptance criteria, technical notes, and implementation details The AI produced a structured breakdown organized into milestones:\nMilestone Focus Example Issues Phase 1 Core HVAC Features Climate entities, temperature control, mode switching, dynamic temperature limits Phase 2 Sensors \u0026amp; Monitoring Temperature sensors, alarm monitoring, device status, operation status Phase 3 Advanced Features Silent mode, fan speed control, OTC monitoring, water heater, swimming pool Each issue looked something like:\n[Enhancement] Add Silent/Quiet Mode Support (#64)\nWhat: Add silent mode control based on the silentMode field from the elements API\nTechnical notes: The JavaScript uses silentMode: 0 for off and silentMode: 1 for on. The value -1 means the feature is not available for this device.\nAcceptance criteria:\nSwitch entity that toggles silent mode Entity is only created when silentMode ≠ -1 Toggle sends POST to /data/indoor/heat_setting with silentMode parameter You can see all these issues on the GitHub issues page.\nStep 5: The AI-Driven Development Workflow # With the architecture defined and the issues created, I entered the implementation phase. Here\u0026rsquo;s the workflow I used consistently throughout the project:\nThe Architect + Coder Model # ┌──────────────────────────────────────────────────────────┐ │ Claude Opus (Architect) │ │ • Analyses JS source + JSON responses │ │ • Designs architecture │ │ • Creates detailed GitHub issues with acceptance criteria│ └──────────────────────┬───────────────────────────────────┘ │ Detailed Issues ▼ ┌──────────────────────────────────────────────────────────┐ │ Claude Sonnet / Copilot (Developer) │ │ • Implements each issue as a PR │ │ • Writes unit tests │ │ • Follows the architecture decisions from above │ └──────────────────────┬───────────────────────────────────┘ │ Pull Request ▼ ┌──────────────────────────────────────────────────────────┐ │ Me (Code Review + Testing) │ │ • Reviews every PR │ │ • Tests on real hardware │ │ • Validates against CSNet Manager web app │ │ • Merges or requests changes │ └──────────────────────────────────────────────────────────┘ Why two models? Using a highly capable model (Claude Opus) for architecture and a faster/cheaper model (Claude Sonnet, GitHub Copilot) for implementation keeps costs reasonable while maintaining quality. The architect model produces detailed enough specifications that a less powerful model can implement them accurately.\nFor each issue:\nThe coding AI creates a feature branch It implements the code following the issue specifications It writes unit tests It creates a PR with a description of all changes I review the code, test on my real Hitachi heat pump, and merge You can see the entire history of this process in the pull requests — each PR is a single feature or bug fix, with a clear description of what was implemented and why.\nStep 6: Community-Driven Refinement — The Secret Weapon # Here\u0026rsquo;s where the project truly came alive. Building the initial integration was one thing — making it work for everyone was another.\nRemember my earlier challenge? I only have one device configuration (two air circuits, no water heater, no pool). How do you support hardware you don\u0026rsquo;t own?\nThe answer: the community.\nWithin weeks of releasing the first version on HACS, 4-5 users with different Hitachi configurations started regularly testing and reporting feedback. Each new tester was like finding a puzzle piece I couldn\u0026rsquo;t buy:\n🔥 One user had a DHW (Domestic Hot Water) heater → We discovered elementType: 3 and the settingTempDHW field, then built the water heater entity 🏊 Another had a swimming pool heater → We found elementType: 4 with its 24-33°C temperature range 🌡️ A user with a Yutaki S2 + Yutampo waterboiler (#52) → Confirmed the integration works with water circuits (elementType: 5 and 6) 💨 Someone with fan coils reported a different speed mapping (#127) → We added legacy vs standard fan speed models 📊 Users asked for more sensors — compressor stats, outdoor temperatures, pump speeds → We surfaced more data from the installationdevices endpoint Each time someone reported \u0026ldquo;I have this configuration and here\u0026rsquo;s my elements JSON\u0026rdquo;, I knew we could expand support. The JSON response from /data/elements became our common debugging language — any user could capture it from their browser and share it (redacting private data) to help identify unmapped fields.\nThe real \u0026ldquo;aha moment\u0026rdquo;: Every time someone said \u0026ldquo;I have this specific setup and I can test\u0026rdquo;, it felt like unlocking a new level. We could never have tested swimming pool or fan coil support without those volunteers.\nCommunity testers also helped catch subtle bugs: wrong temperature readings (#137), incorrect operation status mapping (#122), credential management issues (#153).\nThe Result # Today, the Hitachi CSNet Home integration is a complete Home Assistant custom component with:\nClimate entities per zone with HVAC modes (heat/cool/off), presets (comfort/eco), and target temperature control Water heater entity with eco/performance modes 40+ sensors for temperatures, operation status, alarm monitoring, compressor stats, and more Advanced features: silent mode, fan speed control, OTC (Outdoor Temperature Compensation) monitoring Alarm system with persistent notifications and historical alarm tracking Multi-zone support for C1/C2 air and water circuits Numbers that tell the story:\nMetric Value Closed issues 166+ Releases 29 Contributors 11 Python codebase ~5,000 lines (integration + tests) CI/CD 32+ test combinations across 7+ HA versions HACS ✅ Available AI vs Manual: The Time Factor # Let me be honest about what AI did and didn\u0026rsquo;t do in this project.\nWhat AI excelled at: # Code translation (JS → Python): The AI could read JavaScript source code, understand the logic, and produce equivalent Python in minutes — work that would take hours manually Pattern recognition: Mapping cryptic field names to meaningful constants across thousands of lines of JS Boilerplate generation: Home Assistant integration structure, config flows, entity platforms — all the scaffolding that takes time to write but follows clear patterns Issue decomposition: Breaking a complex project into well-structured, implementable user stories What AI couldn\u0026rsquo;t do: # Test with real hardware. Only real devices connected to the CSNet cloud can validate the integration works Understand edge cases from a single data point. AI could map elementType: 1 to \u0026ldquo;air circuit\u0026rdquo;, but it couldn\u0026rsquo;t know that elementType: 5 encodes temperature differently (multiplied by 10) without seeing the JS logic Replace community interaction. Understanding that some users have legacy fan coils with a different speed mapping required human conversation and debugging The time comparison: # With AI: Core integration in 2-3 days. Full-featured with community refinement in a few weeks Without AI (estimated): Core integration would take 2-3 weeks of reading JavaScript, understanding protocols, writing Python manually. Full-featured? Months. The AI didn\u0026rsquo;t save 80% of the thinking — it saved 80% of the typing and translating. The human work remained essential: making architectural decisions, reviewing code, testing on real hardware, and working with the community.\nYour Turn: A Recipe for Reverse-Engineering Any Web Service # If you want to apply this approach to another undocumented web service, here\u0026rsquo;s the step-by-step:\n1. 🔍 Inspect the Network Traffic # Open DevTools → Network tab Interact with the web app and identify the API calls Look for JSON responses — that\u0026rsquo;s your best-case scenario 2. 📖 Read the JavaScript Source # Check the Sources tab for unminified JS Use prettier or your IDE to format minified code Look for constants, enums, mapping functions 3. 🤖 Feed Everything to an AI # Give the AI: JavaScript source + sample JSON responses + context about what the web app does Ask it to: map every field, identify the data model, create a technical specification Use a capable model (Claude Opus, GPT-4) for this architectural analysis 4. 📋 Create Structured Issues # Ask the AI to produce detailed GitHub issues from the spec Each issue = one feature, with acceptance criteria and technical notes Organize into milestones (core features first, then enhancements) 5. 💻 Implement with AI Assistance # Use a coding AI (Claude Sonnet, Copilot) to implement each issue Keep PRs focused and small Always review the code yourself — AI makes subtle mistakes 6. 👥 Release Early, Get Community Feedback # Don\u0026rsquo;t wait for perfection Users with different configurations will find things you never could Make it easy for them to share data (JSON responses, debug logs) Final Thoughts # What I built here with AI could absolutely be done manually. The process of inspecting network calls, reading JavaScript, understanding data formats — it\u0026rsquo;s all standard reverse-engineering. Developers have been doing it for decades.\nBut the timeframe is completely different. What took days with AI would have taken weeks without it. The AI handled the tedious translation work — reading thousands of lines of JavaScript, mapping constants, generating boilerplate — while I focused on what humans do best: architecture, testing, and community collaboration.\nThe real magic wasn\u0026rsquo;t just the AI. It was the combination of AI-accelerated development and a community of 4-5 dedicated users, each with a unique Hitachi configuration, who gave regular feedback, tested features they needed, and helped the integration grow from a proof of concept into something that genuinely works for everyone.\nLinks:\n📦 Repository: github.com/mmornati/home-assistant-csnet-home 📚 Documentation: mmornati.github.io/home-assistant-csnet-home 💬 Discussions: GitHub Discussions 🛒 HACS: Search for \u0026ldquo;csnet home\u0026rdquo; or \u0026ldquo;Hitachi\u0026rdquo; Have you reverse-engineered a web service with AI? Found a cloud-only IoT device that needed a local integration? I\u0026rsquo;d love to hear about your experience — leave a comment or open a discussion on the repo!\n","date":"25 février 2026","externalUrl":null,"permalink":"/reverse-engineering-hitachis-cloud-api-with-ai-from-browser-devtools-to-a-full-home-assistant-integration/","section":"Posts","summary":"","title":"Reverse-Engineering Hitachi's Cloud API with AI: From Browser DevTools to a Full Home Assistant Integration","type":"posts"},{"content":"","date":"18 janvier 2026","externalUrl":null,"permalink":"/fr/tags/ai/","section":"Tags","summary":"","title":"Ai","type":"tags"},{"content":"","date":"18 janvier 2026","externalUrl":null,"permalink":"/fr/tags/engineering-leadership/","section":"Tags","summary":"","title":"Engineering-Leadership","type":"tags"},{"content":"","date":"18 janvier 2026","externalUrl":null,"permalink":"/fr/tags/futureofwork/","section":"Tags","summary":"","title":"Futureofwork","type":"tags"},{"content":"Nous sommes en 2026. Si vous relisez les prédictions alarmistes de 2023 ou 2024, nous devrions tous être au chômage technique, remplacés par des algorithmes. Pourtant, je regarde autour de moi, dans mes équipes et dans l\u0026rsquo;industrie : nous sommes toujours là.\nMais soyons honnêtes : le métier que j\u0026rsquo;ai exercé pendant trois décennies n\u0026rsquo;existe plus vraiment.\nEn tant que Director of Engineering, et avec le recul de 30 ans de développement, je vois beaucoup de débats stériles sur la question \u0026ldquo;L\u0026rsquo;IA va-t-elle remplacer les devs ?\u0026rdquo;. C\u0026rsquo;est, à mon sens, la mauvaise question. L\u0026rsquo;IA code déjà à notre place. L\u0026rsquo;acte même d\u0026rsquo;écrire le code syntaxique, cette \u0026ldquo;tâche artisanale\u0026rdquo; que nous avons chérie, est devenue une commodité.\nLa vraie question est : qu\u0026rsquo;allons-nous construire maintenant que nous n\u0026rsquo;avons plus besoin de poser chaque brique à la main ?\nDe l\u0026rsquo;Assistant au Partenaire d\u0026rsquo;Architecture # Il y a encore trois ans, nous utilisions des \u0026ldquo;copilotes\u0026rdquo;. C\u0026rsquo;était sympathique : une autocomplétion intelligente qui nous épargnait d\u0026rsquo;aller sur StackOverflow pour une Regex ou une fonction boilerplate.\nAujourd\u0026rsquo;hui, en 2026, le paradigme a changé. Nous sommes passés de l\u0026rsquo;assistant au membre d\u0026rsquo;équipe synthétique. Les LLM (Large Language Models) actuels ne se contentent plus de répondre ; ils connaissent l\u0026rsquo;architecture, effectuent des code reviews, proposent des refactorings sur des modules entiers et comprennent les dépendances invisibles dans notre monolithe ou nos microservices.\nSelon le rapport GitHub Octoverse 2025, plus de 60% du code mis en production aujourd\u0026rsquo;hui n\u0026rsquo;a pas été initialement tapé par un humain. Nous sommes devenus des éditeurs, des superviseurs, des garants de la vision.\nLe mythe du Senior irremplaçable (et la réalité du contexte) # On entend souvent : \u0026ldquo;L\u0026rsquo;IA remplacera les Juniors, mais les Seniors sont à l\u0026rsquo;abri car ils ont l\u0026rsquo;expérience.\u0026rdquo;\nC\u0026rsquo;est une vision dangereusement simpliste. Ce qui distingue un Senior d\u0026rsquo;un Junior, ce n\u0026rsquo;est pas seulement sa capacité à écrire du C++ ou du Python les yeux fermés. C\u0026rsquo;est le contexte. Le Senior sait pourquoi telle décision a été prise il y a trois ans (souvent suite à un incident douloureux en prod), il connaît la \u0026ldquo;culture\u0026rdquo; du code de l\u0026rsquo;entreprise.\nLe problème, c\u0026rsquo;est que cette documentation est souvent tribale, stockée dans nos têtes.\nMais que se passe-t-il quand nous donnons ce contexte à l\u0026rsquo;IA ? Dans les entreprises modernes, nous ne nous contentons plus de donner un IDE à l\u0026rsquo;IA. Nous lui ouvrons nos Architecture Decision Records (ADR), nos post-mortems d\u0026rsquo;incidents, notre documentation Confluence et l\u0026rsquo;historique de nos PRs. C\u0026rsquo;est le principe du RAG (Retrieval-Augmented Generation) poussé à l\u0026rsquo;échelle organisationnelle.\nSi on \u0026ldquo;onboarde\u0026rdquo; une IA comme on le fait pour un Senior, en lui donnant accès à la mémoire de l\u0026rsquo;entreprise, elle commence à prendre des décisions d\u0026rsquo;une maturité surprenante. Elle ne remplace pas le Senior sur la politique ou l\u0026rsquo;humain, mais sur la technique pure ? La barrière s\u0026rsquo;effondre.\nL\u0026rsquo;Histoire se répète : La quête de l\u0026rsquo;efficience # En 30+ ans dans le code, dont 20+ de carrière, j\u0026rsquo;ai vu ce film plusieurs fois.\nIl y a eu l\u0026rsquo;époque où un \u0026ldquo;Senior\u0026rdquo; était celui qui gérait sa mémoire manuellement et connaissait l\u0026rsquo;assembleur.\nPuis les langages de haut niveau sont arrivés.\nPuis Internet et les frameworks ont rendu la connaissance syntaxique moins critique.\nL\u0026rsquo;IDE a apporté l\u0026rsquo;autocomplétion.\nÀ chaque étape, on a cru que c\u0026rsquo;était la fin de l\u0026rsquo;expertise. À chaque étape, la définition de la performance a changé. Le bon développeur n\u0026rsquo;était plus celui qui écrivait vite, mais celui qui trouvait la solution vite.\nAvec l\u0026rsquo;IA, nous vivons le même changement, mais à une échelle logarithmique. La valeur n\u0026rsquo;est plus dans la production de la solution (le code), mais dans la définition du problème et la validation du résultat.\nL\u0026rsquo;Analogie Automobile : Sommes-nous les ouvriers des années 80 ? # C\u0026rsquo;est peut-être l\u0026rsquo;analogie la plus pertinente pour notre industrie. Au 20ème siècle, les usines automobiles étaient remplies d\u0026rsquo;ouvriers qui assemblaient des pièces manuellement. Puis, les robots sont arrivés sur les chaînes de montage.\nLes ouvriers ont-ils tous disparu ? Non. Mais leur métier a muté. Ils ont arrêté de visser des boulons pour devenir des superviseurs de robots, des techniciens de maintenance, ou des concepteurs de processus. La production a explosé, la qualité s\u0026rsquo;est standardisée.\nDans le logiciel, nous y sommes.\nHier : Une équipe de 20 développeurs pour sortir une application complexe.\nAujourd\u0026rsquo;hui (2026) : Une \u0026ldquo;Micro-team\u0026rdquo; de 3 ou 4 personnes. Un Product Manager technique, un Architecte Système (ex-Senior Dev), et un Quality Engineer, tous assistés par une flotte d\u0026rsquo;agents IA.\nOn ne code plus pour la machine, on conçoit pour l\u0026rsquo;humain. Notre point d\u0026rsquo;entrée est l\u0026rsquo;intention (le prompt, la spec), et notre point de sortie est la vérification (est-ce que ça marche ? est-ce que c\u0026rsquo;est ce qu\u0026rsquo;on voulait ?). Tout ce qui est au milieu, la \u0026ldquo;fabrication\u0026rdquo; du code, est délégué.\nConclusion : Ne restons pas tétanisés # Alors, allons-nous perdre notre job ? Ceux qui restent tétanisés dans la peur, accrochés à l\u0026rsquo;idée que \u0026ldquo;pisser du code\u0026rdquo; est leur unique valeur ajoutée : oui, probablement.\nMais pour ceux qui embrassent la tendance, c\u0026rsquo;est l\u0026rsquo;âge d\u0026rsquo;or. Nous sommes libérés des tâches répétitives. Nous pouvons nous concentrer sur l\u0026rsquo;architecture, la sécurité, l\u0026rsquo;expérience utilisateur et la logique métier complexe.\nLe titre de \u0026ldquo;Développeur\u0026rdquo; changera peut-être. Nous deviendrons des \u0026ldquo;Architectes de Solutions\u0026rdquo;, des \u0026ldquo;Ingénieurs Produit\u0026rdquo; ou des \u0026ldquo;Superviseurs d\u0026rsquo;IA\u0026rdquo;. Peu importe le titre. L\u0026rsquo;important est de comprendre que notre rôle n\u0026rsquo;est plus de tenir la truelle, mais de dessiner la cathédrale.\nFormons-nous. Adaptons-nous. Et acceptons que notre plus grande compétence, en 2026, n\u0026rsquo;est pas de savoir comment coder, mais de savoir quoi coder.\nEt vous, comment a évolué votre quotidien de dev ces 2 dernières années ? On en discute en commentaire.\n","date":"18 janvier 2026","externalUrl":null,"permalink":"/fr/lia-ne-nous-remplacera-pas/","section":"Posts","summary":"","title":"L'IA ne nous remplacera pas…","type":"posts"},{"content":"","date":"18 janvier 2026","externalUrl":null,"permalink":"/fr/tags/software-development/","section":"Tags","summary":"","title":"Software-Development","type":"tags"},{"content":"","date":"18 janvier 2026","externalUrl":null,"permalink":"/fr/tags/tech-trends-2026/","section":"Tags","summary":"","title":"Tech-Trends-2026","type":"tags"},{"content":"","date":"17 janvier 2026","externalUrl":null,"permalink":"/tags/coding/","section":"Tags","summary":"","title":"Coding","type":"tags"},{"content":" The Hidden Cost of AI Coding Assistants # If you\u0026rsquo;re using AI coding assistants like GitHub Copilot, Cursor, or Claude, you might not realize how much you\u0026rsquo;re spending on context. Every time your AI needs to understand your codebase, it consumes tokens: the currency of large language models (LLMs).\nBut what are tokens, exactly?\nThink of tokens as the \u0026ldquo;words\u0026rdquo; that AI models understand. They\u0026rsquo;re not exactly words, but pieces of text:\n\u0026quot;Hello, world!\u0026quot; = 4 tokens\nA 500-line Python file ≈ 2,000–4,000 tokens\nYour entire codebase? Potentially hundreds of thousands of tokens\nAnd here\u0026rsquo;s the kicker: you pay for every token. With GPT-4o, that\u0026rsquo;s $2.50 per million input tokens and $10 per million output tokens. It adds up fast.\nThe Problem: Traditional Context is Expensive # When an AI assistant needs to understand your code, it typically does one of these things:\nMethod Token Cost Problem Read entire files 1,000–10,000+ tokens/file Most content is irrelevant Search with grep Variable No semantic understanding Paste code manually User overhead Error-prone, incomplete Load entire codebase 50,000–500,000+ tokens Exceeds most context windows Real example: To understand how a search function works in a project, an AI might need to read:\nserver.py (1,405 lines → 10,270 tokens)\ndatabase.py (554 lines → 3,514 tokens)\nThat\u0026rsquo;s 13,784 tokens just to find a few relevant functions.\nThe Solution: RAG (Retrieval-Augmented Generation) # RAG is a technique that retrieves only the relevant pieces of information before sending them to the AI. Instead of dumping entire files into the context, RAG:\nPre-indexes your codebase into semantic chunks (functions, classes, documentation sections)\nSearches for the most relevant chunks using vector similarity\nReturns only what\u0026rsquo;s needed (typically 500–2,000 characters per result)\nSame example with RAG:\nSearch for \u0026ldquo;search semantic similarity\u0026rdquo; → returns 5 targeted chunks\nToken cost: 1,679 tokens (vs 13,784)\nSavings: 87.8%\nReal Benchmark Results # I built a benchmark script to measure actual token savings using live RAG searches against an indexed codebase.\nVerified Results (Real RAG Searches) # These results use actual semantic search against the nexus-dev project\u0026rsquo;s indexed database:\nTest Case Without RAG With RAG Savings Find embedding function 3,883 tokens 575 tokens 85.2% Understand search flow 13,784 tokens 1,679 tokens 87.8% How chunking works 2,264 tokens 551 tokens 75.7% MCP gateway routing 5,064 tokens 2,958 tokens 41.6% Lesson recording system 13,784 tokens 1,664 tokens 87.9% Total 38,779 tokens 7,427 tokens 80.8% Note: The \u0026ldquo;MCP gateway routing\u0026rdquo; case shows lower savings (41.6%) because the RAG search returned one large chunk (2,174 tokens). This demonstrates that RAG effectiveness depends on how your code is chunked: smaller, focused functions yield better savings.\nWhat the RAG Search Actually Returns # For \u0026ldquo;Find embedding function\u0026rdquo;, instead of 585 lines of embeddings.py, RAG returns:\n🔍 embed: 55 tokens (core embedding function) 🔍 embed_batch: 207 tokens (batch processing) 🔍 embed: 59 tokens (alternative implementation) 🔍 _get_embedder: 92 tokens (factory function) 🔍 embed: 162 tokens (another variant) ───────────────────────────── Total: 575 tokens (vs 3,883 for full file) Cost Impact # Using GPT-4o pricing ($2.50/1M input tokens):\nMetric Without RAG With RAG Monthly Savings* Per task 38,779 tokens 7,427 tokens — Per session (10 tasks) ~388K tokens ~74K tokens — 200 sessions/month 77.6M tokens 14.8M tokens — Monthly cost $194 $37 $157/month *Assuming 200 coding sessions per month with 10 context retrievals each\nHow RAG Works (For Non-Experts) # Let me break down RAG without the jargon:\nStep 1: Indexing (One-Time Setup) # Your Code Vector Database ┌─────────────────┐ ┌─────────────────┐ │ def login(): │ │ [0.12, 0.45...] │ ← \u0026#34;login function\u0026#34; │ check_auth() │ → │ [0.33, 0.21...] │ ← \u0026#34;authentication\u0026#34; │ ... │ │ [0.67, 0.89...] │ ← \u0026#34;user session\u0026#34; └─────────────────┘ └─────────────────┘ Each function, class, and documentation section is converted into a vector: a list of numbers that represents its meaning. Similar concepts have similar vectors.\nStep 2: Searching (Every Query) # When you ask \u0026ldquo;how does authentication work?\u0026rdquo;, RAG:\nConverts your question into a vector\nFinds the most similar vectors in the database\nReturns the corresponding code chunks\nQuery: \u0026#34;authentication\u0026#34; ↓ Vector: [0.35, 0.22, ...] ↓ Match: login() function (similarity: 0.92) ↓ Return: Just the relevant 50 lines, not the entire file Step 3: AI Response # The AI receives only the relevant chunks, answers your question, and you save tokens.\nTools to Measure Your Own Token Usage # LiteLLM (Free, Open-Source) # LiteLLM is an open-source proxy that logs every LLM request with token counts and costs.\nQuick setup:\n# Install pip install litellm # Run as proxy litellm --model openai/gpt-4o --port 4000 Then point your AI tools at http://localhost:4000 instead of the OpenAI API directly. LiteLLM logs:\nInput/output token counts\nCost per request\nLatency\nView the dashboard:\nlitellm --config config.yaml --detailed_debug # Dashboard at http://localhost:4000/ui OpenAI Usage Dashboard # If you\u0026rsquo;re using OpenAI directly, check your usage dashboard to see daily token consumption.\nImplementing RAG for Your Codebase # Option 1: Nexus-Dev (MCP Server) # Nexus-Dev is an open-source project that provides RAG as an MCP (Model Context Protocol) server. It works with Cursor, Copilot, Antigravity, and other MCP-compatible tools.\n# Install pip install nexus-dev # Initialize your project cd your-project nexus-init --project-name \u0026#34;my-project\u0026#34; # Index your code nexus-index src/ docs/ -r Now your AI assistant can use semantic search instead of reading entire files.\nOption 2: LangChain + Vector DB # For custom implementations, use LangChain with a vector database like LanceDB, Pinecone, or ChromaDB:\nfrom langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import LanceDB # Index code embeddings = OpenAIEmbeddings() vectorstore = LanceDB.from_documents(documents, embeddings) # Search results = vectorstore.similarity_search(\u0026#34;authentication function\u0026#34;, k=5) When NOT to Use RAG # RAG isn\u0026rsquo;t always the best choice:\nSituation Better Approach Small files (\u0026lt;100 lines) Just read the file directly Need full context (refactoring) Read entire file One-time questions Manual paste is fine No semantic similarity (config files) Grep/find works better RAG shines when:\n✅ You have a large codebase (\u0026gt;10K lines)\n✅ You ask repeated questions about the same code\n✅ You need cross-project knowledge\n✅ You want to reduce ongoing costs\nMCP Gateway for Tool Consolidation # Beyond RAG for code search, there\u0026rsquo;s another token efficiency win: tool consolidation.\nThe Problem: Tool Definitions Are Expensive # Every MCP tool you expose to an AI consumes tokens in the system prompt. Each tool definition includes:\nName and description (~20-50 tokens)\nParameter schemas with types and descriptions (~50-150 tokens)\nWith multiple MCP servers, this adds up quickly:\nServers Tools Tokens in System Prompt GitHub only 10 1,508 + Home Assistant 18 2,278 + Filesystem 26 2,892 + Database + Slack 36 3,678 And there\u0026rsquo;s a hard limit: VS Code and OpenAI cap tools at 128 per request.\nThe Solution: Gateway Consolidation # Instead of exposing all 36 tools directly, nexus-dev\u0026rsquo;s gateway approach exposes just 3 meta-tools:\nsearch_tools - Find tools by natural language description\nget_tool_schema - Get full parameter details for a tool\ninvoke_tool - Execute any backend tool\nBenchmark Results # Metric Direct Exposure Gateway Reduction Tools in prompt 36 3 33 fewer Tokens per request 3,678 486 86.8% The Trade-off # The gateway isn\u0026rsquo;t free: it requires an extra call to discover tools:\nTraditional: [Request with 36 tools] → Response Gateway: [Request with 3 tools] → search_tools → invoke_tool → Response When is the gateway worth it?\n✅ More than ~10 tools across servers (break-even point)\n✅ Tools you don\u0026rsquo;t use every request\n✅ Approaching the 128 tool limit\n❌ Only 2-3 frequently-used tools (direct exposure is simpler)\nRun the Benchmark # python scripts/benchmark_gateway_tools.py --servers github,homeassistant,filesystem Impact Analysis # Per-Request Savings # 2,406 tokens saved per request\nAt $2.50/1M tokens (GPT-4o input): $0.006015 per request\nSession Savings (100 requests/session) # Tokens saved: 240,600\nCost saved: $0.6015\nMonthly Savings (1000 sessions × 100 requests) # Tokens saved: 240,600,000\nCost saved: $601.50\nKey Takeaways # Token costs add up fast: Reading files directly can consume 20x more tokens than needed\nRAG reduces context costs by 80%+: By returning only relevant chunks\nTool definitions are hidden costs: 36 exposed tools = 3,678 tokens every request\nGateway consolidation saves 86%: 36 tools → 3 meta-tools = massive savings\nMeasure before optimizing: Use the benchmark scripts on your actual setup\nThere are trade-offs: Gateway adds discovery calls, but saves on baseline\nTry It Yourself # Clone the benchmark script:\ngit clone https://github.com/mmornati/nexus-dev.git cd nexus-dev pip install tiktoken python scripts/benchmark_rag_efficiency.py --project-dir . Set up LiteLLM to track your current token usage\nImplement RAG using Nexus-Dev or your preferred stack\nCompare before/after costs over a month\nResources # Nexus-Dev GitHub - Open-source RAG for AI coding assistants\nLiteLLM - Open-source LLM proxy with cost tracking\nOpenAI Tokenizer - Visual token counter\nTiktoken - Python library for counting tokens\nHave questions or want to share your own benchmark results? Open an issue on GitHub or reach out on Mastodon.\n","date":"17 janvier 2026","externalUrl":null,"permalink":"/how-rag-can-cut-your-ai-coding-costs-by-80/","section":"Posts","summary":"","title":"How RAG Can Cut Your AI Coding Costs by 80%","type":"posts"},{"content":"","date":"17 janvier 2026","externalUrl":null,"permalink":"/tags/rag/","section":"Tags","summary":"","title":"Rag","type":"tags"},{"content":"","date":"17 janvier 2026","externalUrl":null,"permalink":"/tags/tokenization/","section":"Tags","summary":"","title":"Tokenization","type":"tags"},{"content":"","date":"17 janvier 2026","externalUrl":null,"permalink":"/tags/agents/","section":"Tags","summary":"","title":"Agents","type":"tags"},{"content":"If you\u0026rsquo;ve played with the Model Context Protocol (MCP) recently, you\u0026rsquo;ve probably felt the power of giving your LLM explicit tools. Being able to say \u0026ldquo;Hey Claude, search my code\u0026rdquo; or \u0026ldquo;Hey helper, restart the server\u0026rdquo; is magical. It turns a chatbox into a command center.\nBut after using MCP on real projects for a while, I hit a wall. Tools are great, but they are passive. They wait for you to drive. I didn\u0026rsquo;t just want a smarter CLI; I wanted a pair programmer. I wanted Agents.\nToday, I\u0026rsquo;m excited to share a major update to Nexus-Dev that brings true, configurable AI Agents to your IDE, powered by the MCP protocol.\nTools vs. Agents: What\u0026rsquo;s the Difference? # Before we dive into the implementation, let\u0026rsquo;s clarify the shift.\nA Tool is a stateless function. readFile(path) is a tool. It does exactly one thing when asked. An Agent is a system with a Goal, a Persona, and Memory.\nAndrew Ng highlighted back in 2024 the power of \u0026ldquo;Agentic workflows\u0026rdquo;: where an AI iteratively plans, executes, and critiques its own work. Two years later, this is no longer just a theory; it\u0026rsquo;s how we build software. Gartner now predicts that 40% of enterprise applications will embed task-specific AI agents by 2026, and we\u0026rsquo;re seeing this unfold in real time.\nIn Nexus-Dev, we\u0026rsquo;re moving from:\nUser: \u0026ldquo;Find the file auth.py. Now read it. Now find the login function. Now explain it.\u0026rdquo;\nTo:\nUser: \u0026ldquo;Ask the Security Auditor to review the authentication flow.\u0026rdquo;\nIntroducing Dynamic Agents # With the latest release, Nexus-Dev scans your project for an agents/ directory. Inside, you can define your own specialized AI team members using simple YAML files.\nHere is what agents/code_reviewer.yaml might look like:\nname: \u0026#34;code_reviewer\u0026#34; display_name: \u0026#34;Code Reviewer\u0026#34; description: \u0026#34;Delegate code review tasks to the Code Reviewer agent.\u0026#34; profile: role: \u0026#34;Senior Code Reviewer\u0026#34; goal: \u0026#34;Identify bugs, security issues, and suggest improvements\u0026#34; backstory: \u0026#34;Expert developer with 10+ years of experience in code quality.\u0026#34; tone: \u0026#34;Professional and constructive\u0026#34; memory: enabled: true rag_limit: 5 search_types: [\u0026#34;code\u0026#34;, \u0026#34;documentation\u0026#34;, \u0026#34;lesson\u0026#34;] When you start your IDE, Nexus-Dev automatically registers a new MCP tool called ask_code_reviewer. When you invoke it, the server instantiates that specific persona, loads its specific memory context, and executes the task.\nGetting Started with Templates # You don\u0026rsquo;t need to write these from scratch. We\u0026rsquo;ve added a CLI command to generate them from best-practice templates:\n# List available templates nexus-agent templates 📋 Available Agent Templates: • API Designer (api_designer) Role: API Architect Model: claude-sonnet-4.5 • Code Reviewer (code_reviewer) Role: Senior Code Reviewer Model: claude-sonnet-4.5 • Debug Detective (debug_detective) Role: Debugging Specialist Model: claude-sonnet-4.5 • Documentation Writer (doc_writer) Role: Technical Writer Model: claude-opus-4.5 • Performance Optimizer (performance_optimizer) Role: Performance Engineer Model: gemini-3-pro • Refactor Architect (refactor_architect) Role: Refactoring Expert Model: gemini-3-deep-think • Security Auditor (security_auditor) Role: Security Analyst Model: claude-opus-4.5 • Test Engineer (test_engineer) Role: QA Engineer Model: gpt-5.2-codex # Create a new agent based on a template nexus-agent init nexus_doc_writer --from-template doc_writer ✅ Created agent from template: doc_writer ✅ Created agent: /Users/mmornati/Projects/nexus-dev/agents/nexus_doc_writer.yaml Next steps: 1. Edit /Users/marco/nexus-dev/agents/nexus_doc_writer.yaml to customize your agent 2. Restart the MCP server to activate this agent 3. Use the \u0026#39;ask_nexus_doc_writer\u0026#39; tool in your IDE Available templates include: code_reviewer, doc_writer, debug_detective, refactor_architect, test_engineer, security_auditor, api_designer, and performance_optimizer.\nThe Technical Challenge: The \u0026ldquo;Refresh\u0026rdquo; Workaround # Now, let\u0026rsquo;s talk about the specific challenges of building this on top of MCP. It wasn\u0026rsquo;t all smooth sailing.\nThe \u0026ldquo;Which Project?\u0026rdquo; Problem # MCP servers are often global system processes (started by your IDE configuration). But your agents are local to your project. When you open VS Code or Cursor, the MCP server starts up, but it doesn\u0026rsquo;t inherently know that you just opened /Users/marco/projects/my-app. It just runs.\nThis means we can\u0026rsquo;t efficiently pre-load your project-specific agents at startup because we don\u0026rsquo;t know where \u0026ldquo;here\u0026rdquo; is yet.\nThe MCP Specification Today # If you\u0026rsquo;ve been following MCP, you know the protocol has matured significantly. The June 2025 update introduced structured tool outputs (making tool responses more reliable) and OAuth-based authorization. The November 2025 revision added the Tasks primitive for asynchronous, long-running operations and improved server discovery via .well-known URLs.\nCrucially, the spec has long supported notifications/tools/list_changed: a mechanism for servers to tell clients \u0026ldquo;my tool list has changed, please re-fetch it.\u0026rdquo;\nThe Client Support Gap # The problem isn\u0026rsquo;t the protocol; it\u0026rsquo;s the client implementations.\nIdeally, when you open a project, the client (IDE) would:\nTell the server \u0026ldquo;Hey, I\u0026rsquo;m in this folder.\u0026rdquo;\nThe server would emit notifications/tools/list_changed.\nThe client would re-fetch the tool list.\nIn practice:\nContext Awareness: Passing the current working directory reliably during the initialization handshake isn\u0026rsquo;t always standardized across different clients.\nNotification Handling: Not all clients react instantly to notifications/tools/list_changed. Some cache tool lists aggressively. Some don\u0026rsquo;t implement the notification handler at all.\nSampling Support: For agents to work, the client must support the MCP Sampling capability (allowing the server to request LLM completions). Not all IDEs fully support this yet.\nThis is an evolving landscape. As MCP clients mature, these gaps will close.\nThe Solution: refresh_agents # To bridge this gap today, we introduced a pragmatic workaround: the refresh_agents tool.\nWhen you start a session, or if you add a new agent YAML file, you (or the model) simply invoke:\nrefresh_agents() This forces the Nexus-Dev server to:\nQuery the IDE for the current active project path (discovered via NEXUS_PROJECT_ROOT environment variable or inferred from context).\nScan the agents/ folder.\nDynamically register the ask_\u0026lt;agent_name\u0026gt; tools.\nEmit notifications/tools/list_changed to tell the client to update its UI.\nIt\u0026rsquo;s a small extra step, but it unlocks the ability to have per-project, fully customized AI teams without needing complex global configuration management.\nTip: The ideal setup is to configure your IDE\u0026rsquo;s MCP settings with NEXUS_PROJECT_ROOT pointing to your project. This eliminates the need for manual refresh in most cases. See the Quick Start Guide for configuration examples.\nWhy This Matters # This update transforms Nexus-Dev from a \u0026ldquo;RAG Search Engine\u0026rdquo; into a \u0026ldquo;Team Management System\u0026rdquo; for your AI. You can now curate the exact help you need.\nRefactoring? Spin up a refactor_architect.\nWriting Docs? Use the doc_writer.\nLearning a new codebase? Ask the onboarding_buddy.\nThe shift from tools to agents mirrors the broader trend in software development: we\u0026rsquo;re moving from commanding machines to collaborating with them. Your AI isn\u0026rsquo;t just a faster grep; it\u0026rsquo;s a teammate with a defined role and responsibility.\nWhat\u0026rsquo;s Next? # The MCP ecosystem is evolving fast. I\u0026rsquo;m keeping an eye on:\nBetter client-side tooling: As IDEs like Cursor and VS Code mature their MCP implementations, the need for refresh_agents will diminish.\nMulti-Agent Collaboration: The ability to have agents talk to each other, a security_auditor that flags issues, which a code_reviewer then addresses, is an active area of research.\nServer Discovery: The MCP Registry (now in general availability preview) will make sharing and discovering useful agents much easier.\nGo ahead and give it a try. The future of coding isn\u0026rsquo;t just about faster typing; it\u0026rsquo;s about better delegating.\nCheck out the documentation on GitHub to get started.\n","date":"17 janvier 2026","externalUrl":null,"permalink":"/from-tools-to-agents-the-evolution-of-nexus-dev/","section":"Posts","summary":"","title":"From Tools to Agents: The Evolution of Nexus-Dev","type":"posts"},{"content":"","date":"17 janvier 2026","externalUrl":null,"permalink":"/tags/mcp-server/","section":"Tags","summary":"","title":"Mcp-Server","type":"tags"},{"content":"","date":"11 janvier 2026","externalUrl":null,"permalink":"/tags/mcp-client/","section":"Tags","summary":"","title":"Mcp-Client","type":"tags"},{"content":"If you\u0026rsquo;ve been using MCP servers with Cursor, VS Code, or other AI-powered IDEs, you\u0026rsquo;ve probably encountered this dreaded warning:\n⚠️ \u0026ldquo;You have configured more than 50 tools. This may degrade performance.\u0026rdquo;\nModern AI coding agents connect to multiple MCP (Model Context Protocol) servers: GitHub, PostgreSQL, Filesystem, Slack, Jira\u0026hellip; Each server exposes multiple tools. Before you know it, you\u0026rsquo;re at 50+ tools, and your AI agent starts struggling.\nIn this article, I\u0026rsquo;ll explain why this happens and how Nexus-Dev solves it with a Gateway architecture that reduces tool count from 50+ down to just 11.\nQuick Context: What is MCP? # MCP (Model Context Protocol) is a standard introduced by Anthropic in November 2024 that allows AI assistants to connect to external tools and data sources. When you install a GitHub MCP server, your AI can create issues, open PRs, and manage repositories.\nThe problem? Each MCP server adds more tools to your AI\u0026rsquo;s context, and there\u0026rsquo;s a limit to how many tools work well together.\nThe Problem: Tool Explosion # How Tools Consume Context # When you configure MCP servers, each tool\u0026rsquo;s definition (name, description, parameters) gets injected into the AI\u0026rsquo;s context window:\nMCP Server Typical Tools GitHub 15-20 (issues, PRs, repos\u0026hellip;) PostgreSQL 5-10 (query, tables\u0026hellip;) Filesystem 8-12 (read, write, list\u0026hellip;) Slack 10-15 (messages, channels\u0026hellip;) 5 servers × 10 tools = 50 tools consuming precious context.\nWhy Performance Degrades # Research shows that AI accuracy can drop from 87% to 54% with context overload. Each tool definition takes tokens away from your actual code and conversation. Platforms like Cursor enforce a hard limit around 40-50 tools to prevent this.\nBut What About Per-Project Configuration? # Modern IDEs now support project-level MCP configuration:\nVS Code: .vscode/mcp.json\nCursor: .cursor/mcp.json\nThis is better than global configuration: you only load relevant servers per project. But even a typical full-stack project might need GitHub + Database + Cloud + Monitoring + Communication tools. That\u0026rsquo;s still 40+ tools for a single project.\nThe Solution: Nexus-Dev as a Gateway # Instead of exposing all tools directly, Nexus-Dev acts as a gateway: a single MCP server that proxies requests to any number of backend servers.\nThe key insight: Your AI agent only sees 11 tools, but can access all 50+ through dynamic discovery.\nHow It Works # AI asks: \u0026ldquo;I need to create a GitHub issue\u0026rdquo;\nNexus-Dev searches its RAG index: finds github.create_issue\nAI invokes: invoke_tool(\u0026quot;github\u0026quot;, \u0026quot;create_issue\u0026quot;, {...})\nNexus-Dev proxies the request to GitHub MCP\nResult returned to AI\nAll through just 11 gateway tools, not 50+.\nThe 11 Gateway Tools # Instead of exposing 50+ tools directly, your AI sees:\nRAG Tools (7): from the previous article:\nsearch_code, search_docs, search_lessons, search_knowledge\nindex_file, record_lesson, get_project_context\nGateway Tools (4): for accessing any backend:\nTool What It Does list_servers Show available MCP backends search_tools Find tools via semantic search get_tool_schema Get full parameter schema invoke_tool Execute tool on any backend Implementation: How It Works # 1. Index Tool Documentation # First, index all your MCP servers\u0026rsquo; tools into the RAG database:\n# Index all configured servers nexus-index-mcp --all # Or index a specific server nexus-index-mcp --server github This stores each tool\u0026rsquo;s description and schema for semantic search.\n2. Semantic Tool Discovery # When the AI asks \u0026ldquo;how do I create a GitHub issue?\u0026rdquo;, it calls search_tools:\n# search_tools(\u0026#34;create github issue\u0026#34;) # Returns: github.create_issue - Creates a new issue # Parameters: owner, repo, title, body... The AI finds the right tool by meaning, not by knowing every tool name upfront.\n3. Tool Invocation with Error Handling # # AI invokes through the gateway invoke_tool(\u0026#34;github\u0026#34;, \u0026#34;create_issue\u0026#34;, { \u0026#34;owner\u0026#34;: \u0026#34;mmornati\u0026#34;, \u0026#34;repo\u0026#34;: \u0026#34;nexus-dev\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Fix login bug\u0026#34; }) Nexus-Dev handles:\nConnection pooling and reuse\nAutomatic retry with exponential backoff\nConfigurable timeouts\nClean error messages\n4. Server Configuration # Servers are configured in .nexus/mcp_config.json:\n{ \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;servers\u0026#34;: { \u0026#34;github\u0026#34;: { \u0026#34;transport\u0026#34;: \u0026#34;stdio\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-y\u0026#34;, \u0026#34;@modelcontextprotocol/server-github\u0026#34;], \u0026#34;env\u0026#34;: { \u0026#34;GITHUB_PERSONAL_ACCESS_TOKEN\u0026#34;: \u0026#34;${GITHUB_TOKEN}\u0026#34; } }, \u0026#34;postgres\u0026#34;: { \u0026#34;transport\u0026#34;: \u0026#34;stdio\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;-y\u0026#34;, \u0026#34;@modelcontextprotocol/server-postgres\u0026#34;, \u0026#34;postgresql://...\u0026#34;] } } } Two transport types supported:\nstdio: Local processes (npm packages, python scripts)\nsse: Remote HTTP servers (cloud-hosted MCP endpoints)\nQuick Setup # # Import from your existing global MCP config nexus-mcp init --from-global # Or add servers manually nexus-mcp add github --command \u0026#34;npx\u0026#34; --args \u0026#34;-y\u0026#34; \\ --args \u0026#34;@modelcontextprotocol/server-github\u0026#34; # Index all tool documentation nexus-index-mcp --all Update your IDE to use only Nexus-Dev:\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;nexus-dev\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;nexus-dev\u0026#34; } } } That\u0026rsquo;s it. One server. 11 tools. Access to everything.\nBefore vs After # Before: Traditional Setup # IDE Config: github: (15 tools) postgres: (8 tools) filesystem: (10 tools) slack: (12 tools) linear: (8 tools) Total: 53 tools in context ⚠️ Warning: Too many tools After: Gateway # IDE Config: nexus-dev: (11 tools) ✅ All 53+ tools accessible via gateway ✅ Minimal context usage ✅ No performance degradation Real Example # You: \u0026#34;Create a GitHub issue for the login bug\u0026#34; AI: Let me find the right tool... [search_tools(\u0026#34;create github issue\u0026#34;)] Found: github.create_issue [invoke_tool(\u0026#34;github\u0026#34;, \u0026#34;create_issue\u0026#34;, { \u0026#34;owner\u0026#34;: \u0026#34;mmornati\u0026#34;, \u0026#34;repo\u0026#34;: \u0026#34;nexus-dev\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Fix login redirect loop\u0026#34;, \u0026#34;labels\u0026#34;: [\u0026#34;bug\u0026#34;] })] ✅ Issue #42 created All through Nexus-Dev\u0026rsquo;s 11 tools.\nBenefits Summary # Traditional Gateway 50+ tools in context 11 tools in context Configure each server in IDE Configure only Nexus-Dev IDE restart to add servers nexus-mcp add dynamically AI must know exact tool names Semantic search finds tools Conclusion # The MCP ecosystem is growing fast, and tool explosion is a real problem. By using Nexus-Dev as a gateway:\nYour AI agent sees only 11 tools\nIt can access 50+ tools through semantic search\nContext usage stays minimal\nConfiguration stays simple\nCombined with the RAG capabilities from the previous article, Nexus-Dev becomes a complete solution for making your AI coding agent smarter and more efficient.\nNexus-Dev is open source: github.com/mmornati/nexus-dev\n","date":"11 janvier 2026","externalUrl":null,"permalink":"/solving-the-mcp-tool-explosion-a-gateway-approach-for-ai-coding-agents/","section":"Posts","summary":"","title":"Solving the MCP Tool Explosion: A Gateway Approach for AI Coding Agents","type":"posts"},{"content":"If you follow me, you know I\u0026rsquo;m a big fan of AI coding assistants. I use them daily, GitHub Copilot, Cursor, Claude, and they\u0026rsquo;ve genuinely transformed how I write code. But there\u0026rsquo;s one thing that has frustrated me for months: these assistants have no memory.\nEvery single time I start a new session, my AI assistant has to re-learn my codebase. It scans files, asks me the same questions, and burns through tokens just to understand context it already knew yesterday. It\u0026rsquo;s like working with a brilliant colleague who gets amnesia every night.\nSo I built Nexus-Dev, an open-source local RAG system that gives AI coding agents persistent memory.\nWhat is RAG? (For Those New to AI) # Before diving in, let\u0026rsquo;s clarify some terms:\nRAG (Retrieval-Augmented Generation) is a technique where, instead of feeding an entire document to an AI, you store information in a searchable database and retrieve only the relevant pieces when needed. Think of it like giving the AI a smart index to your codebase rather than making it read everything.\nMCP (Model Context Protocol) is an open standard introduced by Anthropic that allows AI agents to connect to external tools and data sources. If you\u0026rsquo;ve used GitHub Copilot, Cursor, or Claude with plugins, you\u0026rsquo;re already using MCP.\nEmbeddings are numerical representations of text that capture meaning. Similar texts have similar embeddings, which enables semantic search, finding content by meaning, not just keywords.\nThe Problem: AI Agents Have Amnesia # No Memory Between Sessions # When you close your IDE and reopen it the next day, your AI assistant forgets everything. It doesn\u0026rsquo;t remember the architecture decisions you made, the bugs you fixed together, or the patterns your codebase uses.\nToken Consumption Adds Up # Every session, the AI needs to \u0026ldquo;warm up\u0026rdquo; by reading your codebase again. This consumes tokens, and tokens cost money. Research shows that giving AI agents more context can actually make them perform worse. Accuracy can drop significantly (from 87% to 54%) due to context overload.\nExisting Solutions Are Cloud-Based # Several solutions address this problem:\nQodo: RAG-based code intelligence (proprietary)\nZep and Pieces: Agent memory platforms (cloud-based)\nBut I wanted something local-first (my code never leaves my machine), open-source (I control the stack), and cross-project (knowledge learned in one project helps others).\nThe Solution: How Nexus-Dev Works # The diagram shows the two main flows:\nIndexing (top flow): Source code → Chunker → Embeddings → LanceDB Search (bottom flow): Query → Embeddings → LanceDB → Relevant Results\nStep 1: Language-Aware Chunking # The first insight is that naive text splitting doesn\u0026rsquo;t work for code. Cutting a function in half destroys its meaning.\nInstead, Nexus-Dev uses tree-sitter to parse code into an Abstract Syntax Tree (AST) and extract semantic units: functions, classes, and methods.\n# Each chunk contains rich metadata for better search @dataclass class CodeChunk: content: str # The actual code chunk_type: ChunkType # function, class, method name: str # e.g., \u0026#34;authenticate_user\u0026#34; docstring: str | None # Documentation helps search! signature: str | None # Function signature start_line: int # Precise location end_line: int Supported languages: Python, JavaScript/TypeScript, Java, and Markdown/RST for documentation.\nStep 2: Multi-Provider Embeddings # Embeddings convert code chunks into vectors that capture meaning. Nexus-Dev supports multiple providers:\nProvider Best For OpenAI Easy setup, general purpose Ollama Privacy, offline, free Google/AWS Enterprise environments Voyage AI Best RAG quality ⚠️ Important: Embeddings aren\u0026rsquo;t portable between providers. Switching requires re-indexing.\nFor privacy-focused teams, Ollama runs entirely locally:\n{ \u0026#34;embedding_provider\u0026#34;: \u0026#34;ollama\u0026#34;, \u0026#34;embedding_model\u0026#34;: \u0026#34;nomic-embed-text\u0026#34; } Step 3: LanceDB Vector Storage # LanceDB is a local vector database, no server to run, just a file on disk.\n# Semantic search finds code by meaning, not keywords results = database.search( query=\u0026#34;authentication middleware\u0026#34;, doc_type=DocumentType.CODE, limit=5 ) # Returns the most relevant functions/classes Step 4: The Secret Weapon: Lessons Learned # After fixing a tricky bug, record it:\nrecord_lesson( problem=\u0026#34;JWT validation fails with special characters\u0026#34;, solution=\u0026#34;Use base64url decode instead of base64\u0026#34;, code_snippet=\u0026#34;claims = base64url.decode(token.split(\u0026#39;.\u0026#39;)[1])\u0026#34; ) Next time you encounter a similar issue, the AI finds it automatically. This creates institutional memory that survives team changes.\nGetting Started # # Install pip install nexus-dev # Initialize cd your-project nexus-init --project-name \u0026#34;my-project\u0026#34; --embedding-provider openai # Set API key export OPENAI_API_KEY=\u0026#34;sk-...\u0026#34; # Index your code nexus-index src/ docs/ -r # Verify nexus-status Add to your IDE\u0026rsquo;s MCP configuration:\nFor Cursor (.cursor/mcp.json):\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;nexus-dev\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;nexus-dev\u0026#34; } } } For VS Code (.vscode/mcp.json):\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;nexus-dev\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;nexus-dev\u0026#34; } } } The 7 Tools Your AI Gets # Tool What It Does search_code Find functions, classes by meaning search_docs Search documentation search_lessons Find past solutions search_knowledge Search everything index_file Add files to the knowledge base record_lesson Save debugging insights get_project_context View project stats Real Example # You: \u0026#34;I need to add authentication to the API\u0026#34; AI: Let me search the codebase... [Calls search_code(\u0026#34;authentication middleware\u0026#34;)] Found 3 relevant results: 1. auth_middleware.py:15-45 - JWTAuthMiddleware class 2. user_service.py:23-67 - authenticate_user function No more reading through entire files. The AI finds exactly what\u0026rsquo;s relevant.\nResults # Since deploying Nexus-Dev:\nFaster session starts: AI immediately has context\nReduced token usage: Only fetching what\u0026rsquo;s needed\nCross-project learning: Lessons from one project help others\nAll local: No cloud, no API costs for storage\nNexus-Dev is open source: github.com/mmornati/nexus-dev\n","date":"11 janvier 2026","externalUrl":null,"permalink":"/stop-repeating-yourself-to-ai-how-i-built-a-local-rag-system-for-coding-assistants/","section":"Posts","summary":"","title":"Stop Repeating Yourself to AI: How I Built a Local RAG System for Coding Assistants","type":"posts"},{"content":"","date":"10 janvier 2026","externalUrl":null,"permalink":"/tags/pentesting/","section":"Tags","summary":"","title":"Pentesting","type":"tags"},{"content":"When I started building Cyber Code Academy, a coding challenge platform where users submit Python code that gets executed on my server, I knew I was playing with fire. Letting strangers run code on your infrastructure is basically an open invitation for disaster. But here\u0026rsquo;s the thing — I\u0026rsquo;m not a security expert. I\u0026rsquo;m just a developer who wanted to build something cool for his son.\nSo I did what any modern developer would do: I asked my AI coding assistants for help.\nAnd what happened next was pretty remarkable.\nThe Problem: Generic Security Tools Don\u0026rsquo;t Understand Your App # If you\u0026rsquo;ve ever run a security scanner like OWASP ZAP or Burp Suite against your application, you know the drill: you get a bunch of findings about missing headers, potential XSS vectors, and maybe some SQL injection warnings. These tools are great. Seriously. Use them.\nBut here\u0026rsquo;s what they don\u0026rsquo;t understand:\nYour business logic: Can a user manipulate their XP score by submitting the same solution twice? Your custom attack surface: Can someone escape your Python sandbox by accessing __builtins__? Your architecture: Is your Docker executor properly isolating network access? Generic scanners test for generic vulnerabilities. But when you\u0026rsquo;re building something unique — like a platform that executes untrusted Python code — you need custom tests that understand YOUR specific risks.\nThat\u0026rsquo;s the gap I needed to fill.\nEnter \u0026ldquo;Vibe Coding\u0026rdquo; for Security # If you\u0026rsquo;ve read my previous post about building Cyber Code Academy, you know I\u0026rsquo;m a big fan of what I call \u0026ldquo;vibe coding\u0026rdquo; — the practice of describing what you want to AI assistants (Cursor, GitHub Copilot, or in this case, Antigravity) and letting them generate the code.\nIt turns out this approach works beautifully for security testing.\nHere\u0026rsquo;s why: Security knowledge is vast and specialized. Most developers (myself included) don\u0026rsquo;t have encyclopedic knowledge of every attack vector. But AI assistants do. They\u0026rsquo;ve been trained on OWASP guides, security research papers, and countless examples of both attacks and defenses.\nSo instead of trying to remember every possible SQL injection payload, I just described what I wanted:\n\u0026#34;I need to test if someone can escape my Python sandbox by using getattr() to access __builtins__ and then call exec(). Generate a test that attempts this and reports if it succeeds.\u0026#34; And the AI delivered:\ndef test_namespace_escape_getattr(self): \u0026#34;\u0026#34;\u0026#34;Test namespace escape via getattr\u0026#34;\u0026#34;\u0026#34; namespace_tests = [ \u0026#34;getattr(__builtins__, \u0026#39;exec\u0026#39;, None)(\u0026#39;print(\\\u0026#34;ESCAPED\\\u0026#34;)\u0026#39;)\u0026#34;, \u0026#34;getattr(__builtins__, \u0026#39;__import__\u0026#39;, None)(\u0026#39;os\u0026#39;).system(\u0026#39;id\u0026#39;)\u0026#34;, \u0026#34;getattr(globals(), \u0026#39;__builtins__\u0026#39;, {}).get(\u0026#39;exec\u0026#39;, None)(\u0026#39;print(\\\u0026#34;ESCAPED\\\u0026#34;)\u0026#39;)\u0026#34;, ] for code in namespace_tests: response = self.session.post( f\u0026#34;{self.base_url}/api/v1/execute\u0026#34;, json={ \u0026#34;code\u0026#34;: code, \u0026#34;tests\u0026#34;: [{\u0026#34;name\u0026#34;: \u0026#34;test\u0026#34;, \u0026#34;assertion\u0026#34;: \u0026#34;True\u0026#34;}], \u0026#34;timeout_seconds\u0026#34;: 5 } ) if \u0026#34;ESCAPED\u0026#34; in response.json().get(\u0026#34;output\u0026#34;, \u0026#34;\u0026#34;): self.log_finding( \u0026#34;CRITICAL\u0026#34;, \u0026#34;Namespace escape via getattr\u0026#34;, \u0026#34;Code can escape restricted namespace using getattr\u0026#34; ) I didn\u0026rsquo;t need to know the exact syntax for these bypass techniques — the AI brought that knowledge. I just needed to know what aspect I wanted to test.\nBuilding a Complete Security Test Suite # Over several sessions, I built up a comprehensive security test suite organized into categories that made sense for my application:\nsecurity-tests/production/ ├── recon.py # Endpoint discovery ├── test_auth.py # JWT attacks, token bypass, password policy ├── test_authz.py # IDOR, role escalation, access control ├── test_injection.py # SQL injection, XSS, command injection ├── test_code_exec.py # Sandbox escape, Docker bypass, DoS ├── test_api_security.py # Rate limiting, headers, CORS ├── test_business_logic.py # XP manipulation, score cheating └── run_tests.py # Orchestrator with phased execution Let me walk you through some of the more interesting tests.\nJWT Token Manipulation # One of the classic attacks against JWT-based authentication is the \u0026ldquo;none algorithm\u0026rdquo; attack. Here\u0026rsquo;s what the AI generated for me:\ndef test_jwt_none_algorithm(self): \u0026#34;\u0026#34;\u0026#34;Test JWT \u0026#39;none\u0026#39; algorithm attack\u0026#34;\u0026#34;\u0026#34; # Decode token without verification decoded = jwt.decode( self.access_token, options={\u0026#34;verify_signature\u0026#34;: False} ) # Create token with \u0026#39;none\u0026#39; algorithm payload = decoded.copy() payload[\u0026#34;alg\u0026#34;] = \u0026#34;none\u0026#34; malicious_token = jwt.encode(payload, \u0026#34;\u0026#34;, algorithm=\u0026#34;none\u0026#34;) # Try to use it response = requests.get( f\u0026#34;{self.base_url}/api/v1/dashboard/me\u0026#34;, headers={\u0026#34;Authorization\u0026#34;: f\u0026#34;Bearer {malicious_token}\u0026#34;} ) if response.status_code == 200: self.log_finding( \u0026#34;CRITICAL\u0026#34;, \u0026#34;JWT \u0026#39;none\u0026#39; algorithm accepted\u0026#34;, \u0026#34;Server accepts tokens with \u0026#39;none\u0026#39; algorithm, allowing forgery\u0026#34; ) I honestly didn\u0026rsquo;t know about this attack until the AI generated this test. Now my application correctly rejects these tokens ✓\nSQL Injection Payloads # For input validation testing, the AI generated a comprehensive list of SQL injection payloads:\nsql_payloads = [ \u0026#34;\u0026#39; OR \u0026#39;1\u0026#39;=\u0026#39;1\u0026#34;, \u0026#34;\u0026#39; OR \u0026#39;1\u0026#39;=\u0026#39;1\u0026#39; --\u0026#34;, \u0026#34;admin\u0026#39;--\u0026#34;, \u0026#34;\u0026#39; UNION SELECT NULL--\u0026#34;, \u0026#34;\u0026#39;; DROP TABLE users; --\u0026#34;, ] for payload in sql_payloads: response = self.session.post( f\u0026#34;{self.base_url}/api/v1/auth/login\u0026#34;, json={\u0026#34;username\u0026#34;: payload, \u0026#34;password\u0026#34;: \u0026#34;Test1234!\u0026#34;} ) # Check for SQL errors in response if any(keyword in response.text.lower() for keyword in [\u0026#34;sql\u0026#34;, \u0026#34;syntax error\u0026#34;, \u0026#34;postgresql\u0026#34;]): self.log_finding( \u0026#34;CRITICAL\u0026#34;, \u0026#34;SQL injection in login username\u0026#34;, f\u0026#34;SQL error detected with payload: {payload}\u0026#34; ) Docker Sandbox Escape # Since my platform runs user code in Docker containers, I needed to test for container escape vulnerabilities:\ndef test_docker_socket_access(self): \u0026#34;\u0026#34;\u0026#34;Test accessing Docker socket\u0026#34;\u0026#34;\u0026#34; docker_socket_tests = [ \u0026#34;import socket; s = socket.socket(socket.AF_UNIX); \u0026#34; \u0026#34;s.connect(\u0026#39;/var/run/docker.sock\u0026#39;); print(\u0026#39;DOCKER_ACCESSIBLE\u0026#39;)\u0026#34;, \u0026#34;open(\u0026#39;/var/run/docker.sock\u0026#39;, \u0026#39;r\u0026#39;)\u0026#34;, ] for code in docker_socket_tests: response = self.session.post( f\u0026#34;{self.base_url}/api/v1/execute\u0026#34;, json={\u0026#34;code\u0026#34;: code, \u0026#34;timeout_seconds\u0026#34;: 5} ) if \u0026#34;DOCKER_ACCESSIBLE\u0026#34; in response.json().get(\u0026#34;output\u0026#34;, \u0026#34;\u0026#34;): self.log_finding( \u0026#34;CRITICAL\u0026#34;, \u0026#34;Docker socket accessible from sandbox\u0026#34;, \u0026#34;Code can access Docker socket, allowing container escape\u0026#34; ) Running the Tests: Real Results # Let me show you what happens when we run this against the live production site. Here\u0026rsquo;s the actual output from a test run I did today:\n============================================================ PRODUCTION SECURITY PENETRATION TESTING ============================================================ Target: https://play.pygame.ovh Test User: aitest_security_2025 Start Time: 2026-01-10T10:10:24 ============================================================ PHASE 1: RECONNAISSANCE ============================================================ [+] Found docs at /docs [+] Found: POST /api/v1/auth/register (Status: 422) [+] Found: POST /api/v1/auth/login (Status: 422) [+] Found: GET /api/v1/challenges (Status: 200) [+] Found: POST /api/v1/execute (Status: 401) ... [+] Reconnaissance complete. Found 20 endpoints. PHASE 2: AUTHENTICATION TESTS ============================================================ [+] Test user \u0026#39;aitest_security_2025\u0026#39; created successfully [+] Login successful, tokens obtained [+] JWT \u0026#39;none\u0026#39; algorithm correctly rejected [+] Weak password correctly rejected: short [+] Weak password correctly rejected: nouppercase123 ... [+] Authentication tests complete. Found 0 issues. PHASE 5: CODE EXECUTION TESTS ============================================================ [*] Testing Docker socket access... [*] Testing host filesystem access... [*] Testing network access... [*] Testing namespace escape via getattr... [*] Testing import bypass... ... [+] Code execution tests complete. Found 0 issues. After approximately 68 seconds of automated testing, here\u0026rsquo;s the summary report:\n============================================================ TESTING COMPLETE ============================================================ Total Time: 67.85 seconds Total Findings: 3 - Critical: 0 - High: 0 - Medium: 3 - Low: 0 ============================================================ What the Tests Found # The test suite automatically generates both JSON and Markdown reports. Here\u0026rsquo;s what it found:\nSeverity Finding Description Medium No rate limiting on registration Registration endpoint allows rapid requests Medium Missing security headers CSP, HSTS, X-XSS-Protection not set Medium OpenAPI documentation exposed /docs endpoint publicly accessible Zero critical or high-severity issues. The sandbox is holding strong — no Docker escapes, no SQL injection, no JWT bypasses. But I\u0026rsquo;ve got some housekeeping to do on those security headers.\nWhat AI Gets Right (and Wrong) # After this experience, here\u0026rsquo;s my honest assessment:\nAI Excels At: # ✅ Generating known attack patterns — OWASP Top 10, common bypass techniques, injection payloads. The AI has seen thousands of examples.\n✅ Structuring test suites — Proper organization, error handling, logging. The boilerplate code is solid.\n✅ Documentation — Every test includes docstrings explaining what it\u0026rsquo;s testing and why.\n✅ Covering edge cases — The AI often suggests test cases I wouldn\u0026rsquo;t have thought of.\nWhere Humans Are Still Essential: # ⚠️ Understanding YOUR threat model — You still need to tell the AI what\u0026rsquo;s important to test.\n⚠️ Interpreting results — Is that \u0026ldquo;Sensitive data in /docs\u0026rdquo; finding actually a problem? (In my case, it\u0026rsquo;s an intentional feature for developers.)\n⚠️ Responsible testing — Never run these tests against systems you don\u0026rsquo;t own or without authorization.\n⚠️ Post-exploitation thinking — If an attack succeeds, what\u0026rsquo;s the real-world impact? AI doesn\u0026rsquo;t always connect those dots.\nTry It Yourself: A Quick Start Guide # Want to vibe-code your own security tests? Here\u0026rsquo;s how to get started:\n1. Define Your Attack Surface # Start by listing what makes your application unique:\n\u0026ldquo;My app executes user-submitted code\u0026rdquo; \u0026ldquo;I use JWT tokens with custom claims\u0026rdquo; \u0026ldquo;Users can modify their profile, including avatar uploads\u0026rdquo; 2. Prompt by Category # Work through security categories systematically:\n\u0026#34;Generate authentication security tests for a FastAPI application that uses JWT tokens. Test for: none algorithm attack, token manipulation, authentication bypass, and weak password acceptance.\u0026#34; 3. Iterate and Refine # After the first generation, ask for improvements:\n\u0026#34;Add a test that attempts to access other users\u0026#39; data by modifying the user_id in the JWT payload\u0026#34; 4. Review and Understand # Don\u0026rsquo;t just run the tests blindly. Read through the code. Learn why each attack works (or should be blocked). You\u0026rsquo;ll become a better developer in the process.\n5. Run Responsibly # Always test in a development environment first. Never attack production systems without explicit authorization. And delete those test accounts when you\u0026rsquo;re done.\nConclusion: Security for Everyone # Here\u0026rsquo;s what I\u0026rsquo;ve learned: You don\u0026rsquo;t need to be a security expert to write security tests. You just need to know what questions to ask and have an AI assistant that can provide the answers.\nThe barrier to entry for security testing just got a lot lower. And that\u0026rsquo;s a good thing — because security shouldn\u0026rsquo;t be a luxury reserved for companies with dedicated pentest teams. If you\u0026rsquo;re building software, you should be testing its security. And now, with AI as your pair programmer, you can.\nThe complete security test suite I\u0026rsquo;ve described is open source (link at the end of this post). Feel free to explore, adapt it to your needs, and contribute improvements. And if you want to see it in action, you can try to break Cyber Code Academy yourself.\nSeriously. Give it your best shot :P\nThe security test suite referenced in this article is open source. If you\u0026rsquo;d like access to the complete codebase with all 24+ test scripts, feel free to reach out — I\u0026rsquo;m happy to share more with interested developers.\nRelated Posts:\nBuilding Cyber Code Academy: A \u0026ldquo;Pure Vibe Coding\u0026rdquo; Experiment Securing Python Code Execution: How We Protected Our Server from Untrusted Code Have questions about AI-assisted security testing? Found a vulnerability I missed? Let me know in the comments or reach out on Twitter!\n","date":"10 janvier 2026","externalUrl":null,"permalink":"/vibe-coding-custom-penetration-tests-when-ai-becomes-your-security-partner/","section":"Posts","summary":"","title":"Vibe Coding Custom Penetration Tests: When AI Becomes Your Security Partner","type":"posts"},{"content":"","date":"10 janvier 2026","externalUrl":null,"permalink":"/tags/vibecoding/","section":"Tags","summary":"","title":"Vibecoding","type":"tags"},{"content":"","date":"1 janvier 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"Running user-submitted code on your server is one of the most dangerous things you can do as a developer. A single line of malicious Python could delete your database, steal credentials, or turn your server into a cryptocurrency miner. Yet for platforms like Cyber Code Academy, an interactive Python learning platform, code execution isn\u0026rsquo;t optional. It\u0026rsquo;s the core feature.\nIn this post, I\u0026rsquo;ll walk through how we built a secure, production-ready code execution system using Docker containers, restricted Python namespaces, and multiple layers of defense. We\u0026rsquo;ll explore the attack vectors we protect against, the security measures we implemented, and how each execution flows through our system.\nThe Risks: What Could Go Wrong? # Before diving into our solution, let\u0026rsquo;s understand the threats. When users can submit arbitrary Python code, attackers can attempt:\n1. Namespace Escape # Python\u0026rsquo;s __builtins__ dictionary contains powerful functions like exec(), eval(), compile(), and __import__(). If attackers can access these, they can execute arbitrary code or import dangerous modules.\n# Attack attempt: Access exec via getattr dangerous = getattr(__builtins__, \u0026#39;exec\u0026#39;, None) if dangerous: dangerous(\u0026#34;import os; os.system(\u0026#39;rm -rf /\u0026#39;)\u0026#34;) 2. Filesystem Access # Even without dangerous builtins, attackers might try to read sensitive files:\n/etc/passwd — user accounts\n/proc/self/environ — environment variables (potentially containing database URLs, API keys)\n/var/run/docker.sock — Docker socket (would allow container escape)\n3. Network Access # Malicious code could exfiltrate data or download malware:\nMake HTTP requests to attacker-controlled servers\nOpen socket connections\nAccess internal network resources\n4. Resource Exhaustion (DoS) # Attackers could consume all server resources:\nInfinite loops consuming CPU\nLarge memory allocations\nFile descriptor exhaustion\n5. Container Escape # If running in Docker, attackers might try to:\nAccess the Docker socket to control the host\nMount the host filesystem\nBreak out of container isolation\n6. Code Injection # Various Python mechanisms could be exploited to execute arbitrary code:\neval(), exec(), compile() functions\n__import__() to load dangerous modules\nMetaclass-based attacks\nTo validate our security, we created a comprehensive test suite with 24 security tests covering all these attack vectors. Every test should fail — if any succeeds, we have a vulnerability.\nOur Solution: Defense in Depth # We implemented multiple security layers, each protecting against different attack vectors. If one layer fails, others provide backup protection.\nArchitecture Overview # ┌─────────────────────────────────────┐ │ FastAPI Endpoint │ │ POST /api/v1/execute │ │ (Authentication, Validation) │ └──────────────┬──────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ ExecutorPool Service │ │ - Semaphore (concurrency limit) │ │ - Container lifecycle management │ │ - Resource limit enforcement │ └──────────────┬──────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Docker Container │ │ - Network: none (isolated) │ │ - Capabilities: ALL dropped │ │ - Filesystem: read-only │ │ - Memory: 512MB max │ │ - CPU: 1 core max │ │ - Timeout: 10-30 seconds │ └──────────────┬──────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ executor_entrypoint.py │ │ - Restricted namespace │ │ - Signal-based timeout │ │ - Test execution │ └─────────────────────────────────────┘ Layer 1: Docker Container Isolation # The first line of defense is Docker container isolation. Each code execution runs in a completely isolated container.\nThe Executor Image # Our executor image (infra/docker/executor.Dockerfile) is purpose-built for security:\nFROM python:3.13-slim # Minimal base image - only essential libraries RUN apt-get update \u0026amp;\u0026amp; apt-get install -y --no-install-recommends \\ libffi-dev \\ libssl-dev \\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* # Create non-root user RUN useradd -m -s /sbin/nologin executor WORKDIR /executor # Copy executor entrypoint script COPY --chown=executor:executor executor_entrypoint.py /executor/ # Switch to non-root user USER executor ENTRYPOINT [\u0026#34;python\u0026#34;, \u0026#34;/executor/executor_entrypoint.py\u0026#34;] Key security features:\nMinimal base image: python:3.13-slim contains only essential packages\nNon-root user: Code runs as executor user, not root\nNo unnecessary packages: Reduces attack surface\nContainer Security Flags # When we run the container, we apply strict security constraints:\ncmd = [ \u0026#34;docker\u0026#34;, \u0026#34;run\u0026#34;, \u0026#34;--rm\u0026#34;, # Auto-remove after execution \u0026#34;--memory=512m\u0026#34;, # Memory limit \u0026#34;--memory-swap=512m\u0026#34;, # No swap (prevents swap-based attacks) \u0026#34;--cpus=1.0\u0026#34;, # CPU limit \u0026#34;--network=none\u0026#34;, # No network access \u0026#34;--read-only\u0026#34;, # Read-only root filesystem \u0026#34;--cap-drop=ALL\u0026#34;, # Drop all Linux capabilities \u0026#34;--tmpfs=/tmp:size=10m,mode=1777\u0026#34;, # Only /tmp writable (10MB limit) \u0026#34;-i\u0026#34;, # Interactive stdin for input \u0026#34;cyber-code-executor\u0026#34; ] Let\u0026rsquo;s break down what each flag prevents:\nFlag Protection Against --network=none Network access, data exfiltration, downloading malware --cap-drop=ALL Privilege escalation, system calls requiring capabilities --read-only Writing to filesystem, modifying system files --tmpfs /tmp Limits writable space to 10MB (prevents disk exhaustion) --memory=512m Memory exhaustion DoS attacks --cpus=1.0 CPU exhaustion via infinite loops --rm Ensures container cleanup (no persistent state) Even if malicious code somehow breaks out of Python\u0026rsquo;s restrictions, Docker isolation prevents it from accessing the host system, network, or other containers.\nLayer 2: Restricted Python Namespace # The second layer restricts what Python functions and modules are available to user code. We create a custom __builtins__ dictionary containing only safe functions.\nCreating the Restricted Namespace # Inside executor_entrypoint.py, we build a restricted execution namespace:\nimport builtins exec_namespace = { \u0026#34;__builtins__\u0026#34;: { # Safe built-in functions \u0026#34;print\u0026#34;: print, \u0026#34;len\u0026#34;: len, \u0026#34;range\u0026#34;: range, \u0026#34;str\u0026#34;: str, \u0026#34;int\u0026#34;: int, \u0026#34;float\u0026#34;: float, \u0026#34;list\u0026#34;: list, \u0026#34;dict\u0026#34;: dict, \u0026#34;set\u0026#34;: set, \u0026#34;tuple\u0026#34;: tuple, \u0026#34;zip\u0026#34;: zip, \u0026#34;enumerate\u0026#34;: enumerate, \u0026#34;sorted\u0026#34;: sorted, \u0026#34;sum\u0026#34;: sum, \u0026#34;min\u0026#34;: min, \u0026#34;max\u0026#34;: max, \u0026#34;abs\u0026#34;: abs, \u0026#34;all\u0026#34;: all, \u0026#34;any\u0026#34;: any, \u0026#34;map\u0026#34;: map, \u0026#34;filter\u0026#34;: filter, \u0026#34;bool\u0026#34;: bool, \u0026#34;isinstance\u0026#34;: isinstance, \u0026#34;type\u0026#34;: type, \u0026#34;callable\u0026#34;: callable, \u0026#34;hasattr\u0026#34;: hasattr, \u0026#34;getattr\u0026#34;: getattr, \u0026#34;id\u0026#34;: id, # Limited exception types \u0026#34;Exception\u0026#34;: Exception, \u0026#34;ValueError\u0026#34;: ValueError, \u0026#34;TypeError\u0026#34;: TypeError, \u0026#34;IndexError\u0026#34;: IndexError, \u0026#34;KeyError\u0026#34;: KeyError, # Required for class creation \u0026#34;__build_class__\u0026#34;: builtins.__build_class__, \u0026#34;super\u0026#34;: super, }, \u0026#34;__name__\u0026#34;: \u0026#34;__main__\u0026#34;, \u0026#34;__doc__\u0026#34;: None, } # Execute user code in restricted namespace exec(code, exec_namespace) What\u0026rsquo;s Blocked? # Notice what\u0026rsquo;s not in the namespace:\n❌ eval(), exec(), compile() — Code execution\n❌ __import__() — Module importing\n❌ open(), file() — File operations\n❌ input() — User input\n❌ os, subprocess, sys — System access (not in namespace)\n❌ socket, urllib, requests — Network access (not in namespace)\nWhy getattr is Safe # You might notice getattr is allowed. Couldn\u0026rsquo;t attackers use it to access dangerous functions?\n# This attack attempt fails: dangerous = getattr(__builtins__, \u0026#39;exec\u0026#39;, None) It fails because __builtins__ in our namespace is a dictionary, not the real builtins module. The dictionary only contains the functions we explicitly added. There\u0026rsquo;s no exec key in that dictionary, so getattr returns None.\nTimeout Enforcement # We use signal-based timeout enforcement as a safety net:\nclass TimeoutException(Exception): pass def timeout_handler(signum, frame): raise TimeoutException(\u0026#34;Code execution exceeded timeout limit\u0026#34;) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout_seconds) # Set timeout try: exec(code, exec_namespace) finally: signal.alarm(0) # Cancel alarm The Docker container also has a process-level timeout, providing defense in depth. If user code tries to modify signal handlers, Docker\u0026rsquo;s timeout will still terminate the container.\nLayer 3: Execution Flow # Now let\u0026rsquo;s see how everything works together when a user submits code.\n1. Request Arrives # A user submits code via the API:\nPOST /api/v1/execute { \u0026#34;code\u0026#34;: \u0026#34;def add(a, b): return a + b\u0026#34;, \u0026#34;tests\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;test_add\u0026#34;, \u0026#34;assertion\u0026#34;: \u0026#34;assert add(2, 3) == 5\u0026#34;, \u0026#34;hidden\u0026#34;: false } ], \u0026#34;timeout_seconds\u0026#34;: 10 } 2. ExecutorPool Service # The ExecutorPool service manages container execution:\nclass ExecutorPool: def __init__(self, max_pool_size: int = 5): self.semaphore = asyncio.Semaphore(max_pool_size) # Concurrency limit self.executions: Dict[str, ExecutionResult] = {} async def execute(self, request: ExecutionRequest): async with self.semaphore: # Limit concurrent executions # Prepare input JSON execution_input = { \u0026#34;code\u0026#34;: request.code, \u0026#34;tests\u0026#34;: request.tests, \u0026#34;timeout_seconds\u0026#34;: request.timeout_seconds } # Run in thread pool (Docker is blocking I/O) result = await loop.run_in_executor( None, self._execute_blocking, execution_input, request.execution_id, request.timeout_seconds ) return result Key features:\nSemaphore: Limits concurrent executions (default: 5)\nThread pool: Docker operations are blocking, so we run them in a thread pool to avoid blocking the async event loop\nResult caching: Stores results for later retrieval\n3. Container Execution # The blocking execution function creates and runs the container:\ndef _execute_blocking(self, execution_input: dict, execution_id: str, timeout_seconds: int): # Build docker run command with all security flags cmd = [ \u0026#34;docker\u0026#34;, \u0026#34;run\u0026#34;, \u0026#34;--rm\u0026#34;, \u0026#34;--memory=512m\u0026#34;, \u0026#34;--memory-swap=512m\u0026#34;, \u0026#34;--cpus=1.0\u0026#34;, \u0026#34;--network=none\u0026#34;, \u0026#34;--read-only\u0026#34;, \u0026#34;--cap-drop=ALL\u0026#34;, \u0026#34;--tmpfs=/tmp:size=10m,mode=1777\u0026#34;, \u0026#34;-i\u0026#34;, \u0026#34;cyber-code-executor\u0026#34; ] # Run container with JSON input via stdin result = subprocess.run( cmd, input=json.dumps(execution_input), capture_output=True, text=True, timeout=timeout_seconds + 10 # Buffer for container startup ) # Parse JSON output from stdout result_data = json.loads(result.stdout) return ExecutionResult(**result_data) 4. Inside the Container # The container\u0026rsquo;s entrypoint script (executor_entrypoint.py) reads JSON from stdin:\ndef main(): # Read input from stdin request = json.loads(sys.stdin.read()) code = request.get(\u0026#34;code\u0026#34;, \u0026#34;\u0026#34;) tests = request.get(\u0026#34;tests\u0026#34;, []) timeout = request.get(\u0026#34;timeout_seconds\u0026#34;, 10) # Execute code in restricted namespace result = execute_code(code, tests, timeout) # Output results as JSON to stdout print(json.dumps(result), file=sys.stdout) sys.exit(0) The execute_code function:\nSets up signal-based timeout\nCreates restricted namespace\nExecutes user code with exec(code, exec_namespace)\nRuns test assertions in the same namespace\nCaptures stdout/stderr\nReturns structured results\n5. Results Return # The container outputs JSON to stdout, which the backend parses:\n{ \u0026#34;passed\u0026#34;: true, \u0026#34;test_results\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;test_add\u0026#34;, \u0026#34;passed\u0026#34;: true, \u0026#34;error\u0026#34;: null } ], \u0026#34;error\u0026#34;: null, \u0026#34;output\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;execution_time_ms\u0026#34;: 145 } The container is automatically removed (--rm flag), ensuring no persistent state.\nSecurity Testing # We maintain a comprehensive security test suite with 24 tests covering all attack vectors. Every test should fail — if any succeeds, we have a vulnerability.\nExample Test: Filesystem Access # \u0026#34;\u0026#34;\u0026#34; Test: Attempt to read files from filesystem Risk Level: HIGH \u0026#34;\u0026#34;\u0026#34; result = \u0026#34;SAFE\u0026#34; # Attempt 1: Try using open() directly (should be blocked) try: with open(\u0026#39;/etc/passwd\u0026#39;, \u0026#39;r\u0026#39;) as f: content = f.read() result = f\u0026#34;VULNERABLE: Can read /etc/passwd: {content[:100]}\u0026#34; except Exception as e: error_type = type(e).__name__ if error_type == \u0026#39;NameError\u0026#39;: result = \u0026#34;BLOCKED: open() not available\u0026#34; else: result = f\u0026#34;BLOCKED: open() failed: {str(e)}\u0026#34; print(result) Expected result: \u0026quot;BLOCKED: open() not available\u0026quot; (because open is not in the restricted namespace)\nExample Test: Docker Socket Access # \u0026#34;\u0026#34;\u0026#34; Test: Attempt to access Docker socket Risk Level: CRITICAL \u0026#34;\u0026#34;\u0026#34; result = \u0026#34;SAFE\u0026#34; # Attempt 1: Try to read Docker socket file try: with open(\u0026#39;/var/run/docker.sock\u0026#39;, \u0026#39;rb\u0026#39;) as f: result = \u0026#34;VULNERABLE: Can read Docker socket\u0026#34; except: pass # Attempt 2: Try to connect via socket module try: import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(\u0026#39;/var/run/docker.sock\u0026#39;) result = \u0026#34;VULNERABLE: Can connect to Docker socket\u0026#34; except: pass print(result) Expected result: \u0026quot;SAFE\u0026quot; (because open is blocked and socket cannot be imported)\nTest Categories # Our test suite covers:\nNamespace Escape (4 tests) — Accessing dangerous builtins\nFilesystem Access (3 tests) — Reading/writing files\nNetwork Access (2 tests) — Socket connections, HTTP requests\nDocker Escape (2 tests) — Docker socket, host filesystem\nResource Exhaustion (2 tests) — Memory/CPU DoS\nImport Bypass (3 tests) — Bypassing import restrictions\nCode Injection (2 tests) — eval, exec, compile\nEnvironment Variables (2 tests) — Credential leakage\nAdvanced Techniques (3 tests) — Metaclass attacks, descriptor abuse\nAll tests should fail. Running them regularly ensures our security measures remain effective.\nDefense in Depth: How Layers Work Together # Each security layer protects against different attack vectors:\nDocker isolation prevents access to host system, network, and filesystem\nResource limits prevent DoS attacks (memory, CPU, timeout)\nRestricted namespace prevents code injection and dangerous imports\nNon-root user limits damage if isolation is breached\nRead-only filesystem prevents file modifications\nDropped capabilities prevents privilege escalation\nEven if one layer fails, others provide backup protection. For example:\nIf namespace escape succeeds → Docker isolation prevents damage\nIf Docker escape succeeds → Non-root user limits capabilities\nIf resource limits fail → Timeout enforcement terminates execution\nReal-World Results # In production, our security measures successfully block all attack attempts:\n✅ Namespace escape attempts fail (cannot access exec, eval, __import__)\n✅ Filesystem access attempts fail (open() not in namespace)\n✅ Network access attempts fail (cannot import socket, network is disabled)\n✅ Docker escape attempts fail (Docker socket not mounted, network disabled)\n✅ Resource exhaustion attempts fail (limits enforced, timeouts trigger)\n✅ Code injection attempts fail (dangerous functions not in namespace)\nUsers can write normal Python code (functions, classes, data structures, algorithms), but cannot access system resources or execute arbitrary code.\nPerformance Considerations # Security doesn\u0026rsquo;t come without cost. Our measurements:\nContainer startup: ~200-500ms\nSimple execution: ~50-150ms\nTotal request time: ~250-650ms\nFor a learning platform, this is acceptable. The security benefits far outweigh the performance cost.\nTo optimize:\nPre-build executor images during deployment\nUse Docker layer caching\nIncrease semaphore size for concurrent workloads\nMonitor and optimize container cleanup\nFuture Enhancements # While our current implementation is production-ready, we\u0026rsquo;re considering additional hardening:\nseccomp profiles — Fine-grained system call filtering\nAppArmor/SELinux — Additional kernel-level restrictions\nRestrictedPython library — More robust namespace restrictions via AST transformation\nNetwork namespaces — Custom network policies\nResource quotas — Per-user execution limits\nConclusion # Securing code execution requires multiple layers of defense. By combining Docker container isolation, resource limits, and restricted Python namespaces, we\u0026rsquo;ve created a system that allows users to run code safely while protecting our infrastructure.\nKey takeaways:\nNever trust user code — Always assume it\u0026rsquo;s malicious\nDefense in depth — Multiple security layers provide backup protection\nTest your security — Maintain a comprehensive test suite\nMonitor and log — Track all executions for security auditing\nStay updated — Security is an ongoing process, not a one-time setup\nIf you\u0026rsquo;re building a platform that executes user code, I hope this post provides a solid foundation for your security architecture.\nResources:\nDocker Security Best Practices\nOWASP Code Injection\nPython Sandboxing Guide\n","date":"1 janvier 2026","externalUrl":null,"permalink":"/securing-python-code-execution-how-we-protected-our-server-from-untrusted-code/","section":"Posts","summary":"","title":"Securing Python Code Execution: How We Protected Our Server from Untrusted Code","type":"posts"},{"content":" Introduction # Cyber Code Academy is a modern, gamified platform for mastering Python through interactive challenges, real-time competitions, and AI-powered problem generation. While students focus on solving coding challenges, administrators need robust tools to create, manage, and monitor the platform\u0026rsquo;s content and infrastructure.\nIn this post, we\u0026rsquo;ll take a deep dive into the admin section, a comprehensive suite of tools that simplifies everything from challenge creation to infrastructure monitoring. We\u0026rsquo;ll explore how we leverage JSON storage, semantic validation, AI-powered generation, translation services, and Docker-based execution to create a scalable and maintainable platform.\nThe admin dashboard provides a centralized view of all platform operations\nChallenge Management: Flexible Test Storage and Semantic Validation # JSON-Based Test Storage # One of the core design decisions in Cyber Code Academy was to store challenge tests as JSON in PostgreSQL\u0026rsquo;s JSONB columns. This approach provides several advantages:\nFlexibility: Tests can have different structures (assertion-based, output-based, or custom validation)\nQueryability: PostgreSQL\u0026rsquo;s JSONB operators allow us to query and filter challenges by test properties\nVersioning: Easy to track changes to test suites over time\nNo Schema Migrations: Adding new test types doesn\u0026rsquo;t require database migrations\nEach challenge stores its tests in a JSONB array like this:\n{ \u0026#34;tests\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;test_basic\u0026#34;, \u0026#34;code\u0026#34;: \u0026#34;assert solve([1, 2, 3]) == 6\u0026#34;, \u0026#34;hidden\u0026#34;: false }, { \u0026#34;name\u0026#34;: \u0026#34;test_edge_case\u0026#34;, \u0026#34;code\u0026#34;: \u0026#34;assert solve([]) == 0\u0026#34;, \u0026#34;hidden\u0026#34;: true } ] } The database model uses SQLAlchemy\u0026rsquo;s JSONB type to store this flexible structure:\ntests = Column(JSONB, nullable=False) # Array of test objects The challenge editor shows an UI over JSON structure of tests, making it easy to understand and modify test cases\nSemantic Validation: Beyond Test Results # While unit tests verify that code produces correct outputs, they don\u0026rsquo;t ensure that students are learning the intended concepts. A student might solve a challenge using a workaround or unintended approach that passes all tests but misses the educational objective.\nThis is where semantic validation comes in. We\u0026rsquo;ve implemented a two-tier validation system:\nAST-Based Validation (Fast \u0026amp; Deterministic) # For challenges that require specific code patterns or structures, we use Python\u0026rsquo;s Abstract Syntax Tree (AST) module to perform fast, deterministic validation. The AST validator can check for:\nRequired function definitions\nProhibited imports or functions\nRequired control structures (loops, conditionals)\nCode complexity constraints\nSpecific algorithm requirements\nThe AST validator parses the code into an AST and uses a visitor pattern to check constraints:\nclass ASTValidator: def validate(self, code: str, constraints: Dict[str, Any]) -\u0026gt; ValidationResult: tree = ast.parse(code) visitor = ASTConstraintVisitor(constraints) visitor.visit(tree) return ValidationResult( passed=len(visitor.errors) == 0, errors=visitor.errors, warnings=visitor.warnings ) This approach is:\nFast: No API calls, pure Python parsing\nDeterministic: Same code always produces the same result\nPrecise: Can detect specific code patterns with high accuracy\nAdmins can configure semantic validation constraints for each challenge\nFor admis there is a predefined prompt helping to write a proper AST JSON validator !\nLLM-Based Validation (Flexible \u0026amp; Context-Aware) # For challenges where the learning objective is more nuanced, we use Large Language Models (LLMs) to validate that code follows the challenge instructions. The LLM validator:\nUnderstands the challenge\u0026rsquo;s educational objective\nChecks if the code approach matches the intended learning path\nProvides feedback on code style and best practices\nDetects workarounds that pass tests but miss the point\nThe LLM validator sends the challenge objective, solution code, and user code to an AI model for analysis:\nclass LLMValidator: async def validate(self, code: str, challenge: Challenge, db: AsyncSession): system_prompt = \u0026#34;\u0026#34;\u0026#34;You are a code validator for a Python learning platform. Check if the user\u0026#39;s code follows the challenge instructions exactly.\u0026#34;\u0026#34;\u0026#34; user_prompt = f\u0026#34;\u0026#34;\u0026#34;Challenge Objective: {challenge.description[\u0026#39;objective\u0026#39;]} Expected Approach: {challenge.solution_code} User Code: {code} Analyze if the user\u0026#39;s code follows the challenge instructions.\u0026#34;\u0026#34;\u0026#34; # Call LLM with automatic usage tracking response = await self._call_llm_with_tracking(...) return self._parse_response(response) LLM Fallback Chain: Reliability Through Redundancy # To ensure high availability and handle rate limits, we\u0026rsquo;ve implemented a fallback chain across three LLM providers:\nGroq (Primary): Fast inference with models like llama-3.3-70b-versatile\nGoogle Gemini (Fallback): gemini-2.5-flash for reliable performance\nOpenAI (Last Resort): gpt-4-turbo-preview for maximum quality\nThe system automatically switches providers when:\nRate limits are hit (HTTP 429)\nAPI errors occur\nTimeouts happen\nclass AIModelManager: def handle_error(self, error: Exception, current_model: str): if is_rate_limit_error(error): self.current_index += 1 next_model = self.get_next_model() return True, next_model, retry_after_seconds # ... handle other errors This multi-provider approach ensures that semantic validation remains available even when individual providers have issues, providing a robust and reliable validation system.\nTranslation System: Making Challenges Accessible Globally # Creating quality educational content is time-consuming. Translating that content into multiple languages can be prohibitively expensive and slow. To solve this, we\u0026rsquo;ve integrated LibreTranslate—an open-source translation service—to automatically translate challenges.\nMulti-Language Support with JSONB # Similar to our test storage approach, we use JSONB columns to store translations:\ntitle_i18n = Column(JSONB, nullable=True) # {\u0026#34;en\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;fr\u0026#34;: \u0026#34;...\u0026#34;} description_i18n = Column(JSONB, nullable=True) # Nested structure hints_i18n = Column(JSONB, nullable=True) # Array of translated hints This structure allows us to:\nStore multiple languages in a single row\nQuery by language efficiently\nAdd new languages without schema changes\nMaintain translation history\nAuto-Translation Workflow # The translation system provides a seamless workflow for admins:\nCreate Challenge in English: Write the challenge with all content in English\nAuto-Translate: Click a button to translate to target language (e.g., French)\nReview \u0026amp; Edit: Review the auto-translated content and make manual adjustments\nPublish: The challenge is now available in both languages\nThe translation service uses Redis caching to avoid redundant API calls:\nclass TranslationService: async def translate(self, text: str, target_lang: str, source_lang: str): # Check Redis cache first cache_key = f\u0026#34;translation:{source_lang}:{target_lang}:{hash(text)}\u0026#34; cached = await self.redis.get(cache_key) if cached: return cached.decode(\u0026#39;utf-8\u0026#39;) # Call LibreTranslate API translated = await self._call_libretranslate(text, source_lang, target_lang) # Cache the result await self.redis.setex(cache_key, ttl, translated) return translated This caching strategy:\nReduces API costs\nImproves response times\nHandles repeated translations (e.g., common phrases)\nThe translation editor shows side-by-side comparison of original and translated content\nGraceful Degradation # The translation system is designed to degrade gracefully:\nIf LibreTranslate is unavailable, admins can still manually translate\nCached translations remain available even if the API is down\nThe system logs warnings but doesn\u0026rsquo;t block challenge creation\nAI Challenge Generator: From Concept to Complete Challenge # Creating high-quality coding challenges is an art. It requires:\nClear problem statements\nAppropriate difficulty levels\nComprehensive test cases\nEngaging narratives (in our case, cyberpunk-themed)\nValidated solutions\nTo scale challenge creation, we built an AI Challenge Generator that can create complete challenges from simple specifications.\nHow It Works # The generator takes minimal input:\nCategory: e.g., \u0026ldquo;loops\u0026rdquo;, \u0026ldquo;functions\u0026rdquo;, \u0026ldquo;lists\u0026rdquo;\nDifficulty: \u0026ldquo;initiate\u0026rdquo;, \u0026ldquo;hacker\u0026rdquo;, \u0026ldquo;elite\u0026rdquo;, or \u0026ldquo;legend\u0026rdquo;\nConcept: The educational concept to teach\nContext: A cyberpunk narrative theme\nConstraints: Optional special requirements\nFrom this, it generates:\nA complete challenge description with narrative\nStarter code for students\nSolution code with comments\nComprehensive test suite (visible and hidden tests)\nHints for struggling students\nThe Generation Process # Prompt Engineering: The system uses carefully crafted prompts that instruct the AI to:\nFollow the cyberpunk theme\nCreate progressive difficulty\nInclude comprehensive tests\nReturn valid JSON matching our schema\nSchema Validation: Generated JSON is validated against a JSON Schema to ensure:\nAll required fields are present\nData types are correct\nStructure matches our challenge model\nSolution Testing: The generated solution code is automatically executed against the generated tests to verify:\nAll tests pass\nThe solution is correct\nNo syntax errors exist\nRefinement Loop: If tests fail, the system:\nSends the error back to the AI\nRequests corrections\nRe-validates until tests pass (up to 3 attempts)\nasync def generate_challenge(self, category, difficulty, concept, context): for attempt in range(max_retries): # Call AI with model fallback response = await self._call_llm(messages, model=current_model) challenge_json = self._extract_json(response) # Validate schema self._validate_schema(challenge_json) # Test solution test_result = await self._test_solution(challenge_json) if not test_result[\u0026#34;passed\u0026#34;]: # Request correction messages.append({\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: refinement_prompt}) continue return challenge_json Admins can generate complete challenges with just a few inputs\nModel Fallback for Reliability # The generator uses the same multi-provider fallback system as semantic validation:\nTries Groq first (fast and cost-effective)\nFalls back to Gemini if rate limited\nUses OpenAI as last resort for maximum quality\nThis ensures challenge generation remains available even during provider outages.\nAI Usage Tracking: Understanding Costs and Performance # When using multiple AI providers with different pricing models, understanding usage and costs becomes critical. We\u0026rsquo;ve built comprehensive tracking that logs every AI API call.\nWhat We Track # For every AI call, we log:\nProvider \u0026amp; Model: Which service and model was used\nCall Type: Generation, refinement, or validation\nStatus: Success, error, or rate limit\nPerformance: Response time in milliseconds\nToken Usage: Input tokens, output tokens, total tokens\nCost Estimation: Estimated cost based on provider pricing\nRate Limit Info: Retry-after headers and rate limit status\nMetadata: Full response headers, error details, and context\nThis data is stored in the ai_call_logs table:\nclass AICallLog(Base): provider = Column(String(50), nullable=False, index=True) model = Column(String(100), nullable=False, index=True) call_type = Column(String(50), nullable=False) status = Column(String(20), nullable=False, index=True) response_time_ms = Column(Integer, nullable=True) input_tokens = Column(Integer, nullable=True) output_tokens = Column(Integer, nullable=True) total_tokens = Column(Integer, nullable=True) cost_estimate = Column(Numeric(10, 6), nullable=True) # ... more fields Usage Dashboard # The admin dashboard provides comprehensive analytics:\nTotal Usage: Calls, tokens, and costs over time\nProvider Breakdown: Which providers are used most\nModel Performance: Success rates and response times per model\nCost Analysis: Spending trends and projections\nError Tracking: Rate limits, failures, and retry patterns\nThe AI usage dashboard shows comprehensive statistics on API calls, costs, and performance\nAutomatic Tracking # Every AI call is automatically tracked without requiring manual instrumentation:\nasync def _call_llm_with_tracking(self, provider, model, prompts, db): # Create call log entry call_log = AICallLog( provider=provider_name, model=model_name, status=CallStatus.PENDING.value ) db.add(call_log) await db.flush() try: # Make API call response = await provider.generate_text(...) # Update with success data call_log.status = CallStatus.SUCCESS.value call_log.input_tokens = response.usage.input_tokens call_log.output_tokens = response.usage.output_tokens call_log.cost_estimate = calculate_cost(...) except Exception as e: # Update with error data call_log.status = CallStatus.ERROR.value call_log.error_message = str(e) return response This automatic tracking ensures we never miss a call and can accurately analyze costs and performance.\nExecutor Monitoring: Ensuring Reliable Code Execution # Code execution is the heart of a coding platform. Students submit code, and the system must execute it securely and reliably. We use Docker containers for isolation, and comprehensive monitoring to ensure everything works correctly.\nDocker-Based Secure Execution # Each code submission runs in an isolated Docker container with:\nResource Limits: CPU and memory constraints\nNetwork Isolation: No external network access\nTimeout Enforcement: Automatic termination of long-running code\nClean Environment: Fresh container for each execution\nThe executor service manages a pool of containers to handle concurrent submissions efficiently.\nHealth Monitoring # The admin section provides real-time monitoring of the executor infrastructure:\nDocker Connection: Is Docker daemon accessible?\nImage Status: Is the executor image present and up-to-date?\nPool Metrics: Current pool size, active executions, available slots\nUtilization: Percentage of pool capacity in use\nReal-time monitoring of executor pool health and status\nExecution Statistics # Beyond health checks, the system tracks:\nTotal Executions: Number of code runs over time\nSuccess Rate: Percentage of successful executions\nAverage Execution Time: Performance metrics\nUser Statistics: Per-user execution patterns\nChallenge Statistics: Which challenges have the most submissions\nDebugging Failed Tests # When AI-generated tests fail or students report issues, admins need to debug. The executor monitoring system provides:\nExecution History: View all executions with filters (user, challenge, date range)\nFailed Execution Logs: Full stdout/stderr for failed runs\nTest Results: Detailed test output showing which tests passed/failed\nThis is particularly valuable for AI-generated challenges. Even after manual review, some edge cases might be missed. The execution logs help identify:\nTest cases that are too strict\nEdge cases not covered by tests\nPerformance issues with test execution\nSyntax errors in generated test code\nAdmins can view detailed logs from failed executions to debug test issues\nExample: Debugging an AI-Generated Test # Imagine an AI-generated challenge has a test that\u0026rsquo;s failing unexpectedly:\nAdmin views the challenge in the admin panel\nChecks execution history for that challenge\nFinds a failed execution\nViews the execution logs\nSees the test error: AssertionError: Expected [1, 2, 3] but got [1, 2, 3]\nRealizes the test is comparing lists with == which works, but the error message suggests a different issue\nReviews the test code and fixes the assertion\nRe-tests the challenge\nThis workflow makes it easy to identify and fix issues in AI-generated content, ensuring quality even when challenges are created automatically.\nConclusion # The admin section of Cyber Code Academy demonstrates how thoughtful tooling can simplify complex platform management. By leveraging:\nJSONB storage for flexible, queryable data structures\nSemantic validation (AST + LLM) to ensure educational quality\nMulti-provider AI fallback for reliability\nAuto-translation to scale content globally\nAI generation to create challenges at scale\nComprehensive logging to understand costs and performance\nExecutor monitoring to ensure reliable code execution\nWe\u0026rsquo;ve created a platform that can scale from a few challenges to thousands, from one language to many, and from manual creation to AI-assisted generation—all while maintaining quality and reliability.\nThe admin tools don\u0026rsquo;t just make life easier for administrators; they enable the platform to grow and evolve. As we add more challenges, support more languages, and leverage AI more extensively, these tools ensure we can manage complexity without sacrificing quality.\n","date":"31 décembre 2025","externalUrl":null,"permalink":"/behind-the-scenes-the-admin-section-of-cyber-code-academy/","section":"Posts","summary":"","title":"Behind the Scenes: The Admin Section of Cyber Code Academy","type":"posts"},{"content":"","date":"31 décembre 2025","externalUrl":null,"permalink":"/tags/code/","section":"Tags","summary":"","title":"Code","type":"tags"},{"content":"","date":"31 décembre 2025","externalUrl":null,"permalink":"/tags/learning/","section":"Tags","summary":"","title":"Learning","type":"tags"},{"content":" Introduction # If you\u0026rsquo;ve ever deployed a web application, you know the pain: push a small frontend change, wait for the entire platform to restart, and watch your users experience downtime. For the Cyber Code Academy platform, an interactive Python learning platform with real-time competitions, this was the reality. Every deployment meant 2-3 minutes of complete outage, even for the smallest UI tweak.\nThe culprit? A monolithic Docker Compose setup where every service was tightly coupled. Change the frontend? Restart the database. Update the backend? Restart everything. It was frustrating, inefficient, and frankly, unprofessional.\nI decided it was time for a something different and more professional, even for a free test website. I migrated from a single docker-compose.prod.yml file orchestrating everything to a decoupled, three-tier architecture that enables true zero-downtime deployments on Coolify. The result? I can now deploy frontend changes without touching the database, update the backend independently, and keep the infrastructure services running 24/7 (almost 😂)\nIn this post, I\u0026rsquo;ll walk you through our journey: the problems we faced, the architecture we designed, and how we implemented it. Whether you\u0026rsquo;re running a similar setup or just curious about zero-downtime deployments, I hope this experience helps you avoid the pitfalls I encountered.\nThe Problem: Monolithic Deployment Pain # Let me start by explaining what we had and why it was problematic.\nWhat is a Monolithic Deployment? # In our original setup, we had a single docker-compose.prod.yml file that defined all our services: PostgreSQL database, Redis cache, LibreTranslate translation service, our FastAPI backend, and our Next.js frontend. When Coolify detected a change (like a new commit to the repository), it would:\nStop all containers\nRebuild any changed services\nStart all containers again\nWait for health checks to pass\nThis is what I call a \u0026ldquo;monolithic deployment\u0026rdquo;—everything is bundled together, and everything restarts together. It\u0026rsquo;s simple to understand, but it comes with significant drawbacks.\nReal-World Impact # The real-world impact was brutal. Here\u0026rsquo;s what happened during a typical deployment:\nScenario 1: Frontend UI Fix\nI push a small CSS fix to improve button styling\nCoolify detects the change and triggers a redeploy\nAll services stop: database, Redis, backend, frontend\nDatabase restarts (unnecessary, but required by the monolith)\nRedis restarts (unnecessary)\nBackend restarts (unnecessary)\nFrontend rebuilds and restarts\nTotal downtime: 3-4 minutes\nUsers see \u0026ldquo;Service Unavailable\u0026rdquo; errors\nScenario 2: Backend API Update\nI add a new endpoint for user profiles\nSame process: everything stops, everything restarts\nDatabase connections are dropped mid-request\nActive user sessions are lost\nTotal downtime: 4-5 minutes\nScenario 3: Infrastructure Change\nI need to update PostgreSQL configuration\nThis is the only scenario where a full restart makes sense\nBut even here, we\u0026rsquo;re restarting the frontend unnecessarily\nSpecific Pain Points # Let me break down the specific problems we faced:\n1. Database Restarts on Frontend Changes The most frustrating issue: updating a React component would cause our PostgreSQL database to restart. This made no sense—the database had nothing to do with the frontend change. But because everything was in one Docker Compose file, Coolify treated it as one unit.\n2. Long Outage Windows Our deployments took 2-5 minutes on average. During this time:\nUsers couldn\u0026rsquo;t log in\nActive sessions were lost\nAPI requests failed\nReal-time features (like our coding battles) disconnected\n3. No Independent Updates There was no way to update just the frontend or just the backend. Every change required a full platform restart. This slowed down our development cycle and made us hesitant to deploy small fixes.\n4. Resource Waste We were restarting services that didn\u0026rsquo;t need to restart. PostgreSQL, Redis, and LibreTranslate are stable services that rarely change. Restarting them on every deployment was wasteful and risky.\n5. Deployment Anxiety Because every deployment meant downtime, we started batching changes. Instead of deploying small fixes immediately, we\u0026rsquo;d wait until we had multiple changes. This meant bugs stayed in production longer than necessary. Usually to led user play with the pygame, this meant night deployment 😩 Welcome back in the 80s\nUnderstanding the Architecture # Before diving into the solution, let me explain the architecture concepts we\u0026rsquo;re working with. If you\u0026rsquo;re already familiar with microservices and container orchestration, feel free to skip ahead. But I want to make sure everyone understands the \u0026ldquo;why\u0026rdquo; behind our decisions.\nThe Three-Tier Architecture Concept # Instead of one monolithic deployment, we split our platform into three distinct layers, each with different characteristics and update frequencies:\nInfrastructure Layer: Stable services that rarely change\nBackend Layer: API application that changes moderately\nFrontend Layer: User interface that changes frequently\nThis separation allows us to update each layer independently, which is the key to zero-downtime deployments.\nLayer 1: Infrastructure (The Stable Foundation) # The infrastructure layer contains services that form the foundation of our platform. These services are stable, well-tested, and rarely need updates.\nPostgreSQL Database\nStores all application data: users, challenges, submissions, battles\nRarely changes: maybe a configuration tweak once a quarter\nCritical: if it goes down, the entire platform is unusable\nResource-intensive: needs consistent memory and CPU\nRedis Cache\nHandles session storage and leaderboard caching\nEphemeral data: can be rebuilt if needed\nFast: restarts quickly, but still unnecessary to restart on every deployment\nLightweight: minimal resource usage\nLibreTranslate\nProvides automatic translation for our international users\nPre-loaded models: takes time to start up (60-120 seconds)\nStable: we update it maybe once a year\nResource-intensive: loads language models into memory\nExecutor Builder\nBuilds the Docker image used for code execution\nBuild-only service: creates an image but doesn\u0026rsquo;t run as a container\nCritical for our code execution features\nOnly needs to rebuild when we change security policies or execution environment\nWhy These Rarely Change These services are infrastructure—they\u0026rsquo;re the foundation, not the application. Think of them like the foundation of a house: you don\u0026rsquo;t rebuild the foundation when you repaint the walls. Similarly, we don\u0026rsquo;t need to restart the database when we update the frontend.\nLayer 2: Backend (The Business Logic) # The backend layer contains our FastAPI application—the brain of our platform.\nFastAPI Application\nHandles all API logic: authentication, challenge validation, battle management\nChanges frequently: new features, bug fixes, performance improvements\nDepends on Infrastructure: needs database and Redis to function\nStateless: can be scaled horizontally (run multiple instances)\nKey Characteristics\nUpdates weekly or bi-weekly as we add features\nNeeds to connect to database and Redis (via container names)\nRequires Docker socket access for code execution features\nHas health checks to ensure it\u0026rsquo;s ready before accepting traffic\nWhy It\u0026rsquo;s Separate The backend changes more frequently than infrastructure but less frequently than the frontend. By separating it, we can:\nDeploy backend updates without touching the database\nScale backend independently\nRoll back backend changes without affecting infrastructure\nLayer 3: Frontend (The User Interface) # The frontend layer contains our Next.js application—what users see and interact with.\nNext.js Application\nServes the user interface: dashboards, challenge browser, battle arena\nChanges most frequently: UI improvements, bug fixes, new pages\nDepends on Backend: makes API calls to the backend\nStateless: can be scaled horizontally\nKey Characteristics\nUpdates multiple times per week (sometimes daily)\nOnly needs the backend API URL to function\nBuilds at deployment time (static assets generated during Docker build)\nHas health checks to ensure it\u0026rsquo;s serving pages correctly\nWhy It\u0026rsquo;s Separate The frontend changes the most frequently. By separating it:\nWe can deploy UI fixes instantly without database restarts\nUsers see updates faster\nWe can A/B test different frontend versions\nFrontend developers can deploy independently\nThe Network: How Services Communicate # All three layers communicate over a shared Docker network called cybercodeacademy-proxy. This is crucial for the architecture to work.\nContainer Name Resolution Docker provides DNS-based service discovery. When services are on the same network, they can find each other by container name:\nBackend finds database at: cybercodeacademy-db\nBackend finds Redis at: cybercodeacademy-redis\nBackend finds translator at: cybercodeacademy-translate\nFrontend finds backend at: configured via environment variable (domain or internal DNS)\nWhy This Matters Instead of using localhost or IP addresses (which change), we use container names. Docker\u0026rsquo;s internal DNS resolves these names to the correct container IPs, even when containers restart or move to different hosts.\nExternal Network The cybercodeacademy-proxy network is marked as external: true, meaning it exists outside of any single Docker Compose file. This allows:\nInfrastructure services (from docker-compose.infra.yaml) to join the network\nBackend service (from Coolify) to join the network\nFrontend service (from Coolify) to join the network\nAll services to communicate with each other\nThis is the glue that holds our decoupled architecture together.\nThe Solution: Decoupled Architecture # Now that we understand the architecture, let\u0026rsquo;s dive into how we implemented it. The migration involved three main changes: restructuring our files, configuring Coolify resources, and setting up the network.\nBreaking Down the Monolith # The first step was to split our single docker-compose.prod.yml into separate, focused files.\nFile Structure Changes # Before:\ncyber-code-academy/ ├── docker-compose.prod.yml ← Everything in one file ├── backend/ │ └── Dockerfile └── frontend/ └── Dockerfile After:\ncyber-code-academy/ ├── docker-compose.infra.yaml ← Infrastructure only ├── docker-compose.dev.yml ← Local development (full stack) ├── docker-compose.prod.yml ← DEPRECATED (kept for reference) ├── backend/ │ └── Dockerfile ← Standalone backend image └── frontend/ └── Dockerfile ← Standalone frontend image docker-compose.infra.yaml This file contains only the infrastructure services:\nPostgreSQL (cybercodeacademy-db)\nRedis (cybercodeacademy-redis)\nLibreTranslate (cybercodeacademy-translate)\nExecutor Builder (builds the executor image)\nHere\u0026rsquo;s a simplified version of what it looks like:\nservices: app-db: image: postgres:15-alpine container_name: my-db restart: always environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres_data:/var/lib/postgresql/data networks: - cybercodeacademy-proxy # ... health checks, resource limits, etc. redis: image: redis:7-alpine container_name: my-redis restart: always networks: - cybercodeacademy-proxy # ... configuration libretranslate: image: libretranslate/libretranslate:latest container_name: my-translate restart: always networks: - cybercodeacademy-proxy # ... configuration networks: cybercodeacademy-proxy: external: true name: ${PROXY_NETWORK:-coolify} Notice that:\nAll services use the same external network\nContainer names are explicit (for DNS resolution)\nNo backend or frontend services—those are deployed separately\nBackend Dockerfile The backend Dockerfile remains mostly the same, but we ensure it works with different build contexts:\nFROM python:3.13-slim # Build context can be repository root (.) or backend directory (/backend) ARG SOURCE_PATH=backend/ ARG REQUIREMENTS_PATH=backend/ WORKDIR /app # Install dependencies COPY ${REQUIREMENTS_PATH}requirements.txt ./requirements.txt RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY --chown=appuser:appgroup ${SOURCE_PATH} /app/ # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \\ CMD curl -f http://localhost:8000/ || exit 1 # Start application CMD [\u0026#34;uvicorn\u0026#34;, \u0026#34;app.main:app\u0026#34;, \u0026#34;--host\u0026#34;, \u0026#34;0.0.0.0\u0026#34;, \u0026#34;--port\u0026#34;, \u0026#34;8000\u0026#34;] Frontend Dockerfile The frontend uses a multi-stage build for optimization:\n# Stage 1: Dependencies FROM node:20-alpine AS deps WORKDIR /app COPY frontend/package.json frontend/pnpm-lock.yaml* ./ RUN corepack enable pnpm \u0026amp;\u0026amp; pnpm install --frozen-lockfile # Stage 2: Builder FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY frontend/ . ENV NEXT_PUBLIC_API_URL=${PUBLIC_API_URL} RUN pnpm build # Stage 3: Runner FROM node:20-alpine AS runner WORKDIR /app COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static EXPOSE 3000 CMD [\u0026#34;node\u0026#34;, \u0026#34;server.js\u0026#34;] The key point: both Dockerfiles are designed to work independently, without requiring a full Docker Compose orchestration.\nCoolify Configuration # The magic happens in Coolify, where we configure three separate resources.\nResource 1: Infrastructure (Docker Compose) # Type: Docker Compose Purpose: Deploy stable infrastructure services File: docker-compose.infra.yaml\nConfiguration Steps:\nCreate a new Coolify resource\nSelect \u0026ldquo;Docker Compose\u0026rdquo; as the type\nUpload docker-compose.infra.yaml\nSet environment variables:\nPOSTGRES_USER=pguser POSTGRES_PASSWORD=\u0026lt;strong-password\u0026gt; POSTGRES_DB=my-db PROXY_NETWORK=cybercodeacademy-proxy EXECUTOR_IMAGE_NAME=my-executor Configure the external network: cybercodeacademy-proxy\nDeploy\nKey Points:\nThis resource deploys once and rarely updates\nAll infrastructure services run here\nThe executor image is built automatically\nNetwork is external, shared with other resources\nResource 2: Backend (Public Repository) # Type: Public Repository Purpose: Deploy the FastAPI backend independently Repository: mmornati/cyber-code-academyDockerfile: backend/DockerfileBuild Context: /backend (backend directory)\nConfiguration Steps:\nCreate a new Coolify resource\nSelect \u0026ldquo;Public Repository\u0026rdquo;\nConnect GitHub repository: mmornati/cyber-code-academy\nSet Dockerfile path: backend/Dockerfile\nSet build context: /backend\nEnable auto-redeploy on commits\nConfigure external network: cybercodeacademy-proxy\nSet environment variables:\nDATABASE_URL=postgresql+asyncpg://pguser:\u0026lt;password\u0026gt;@mydb-db:5432/mydb REDIS_URL=redis://my-redis:6379 JWT_SECRET=\u0026lt;secret\u0026gt; JWT_REFRESH_SECRET=\u0026lt;refresh-secret\u0026gt; ENVIRONMENT=production EXECUTOR_IMAGE_NAME=my-executor DOCKER_HOST=unix:///var/run/docker.sock LIBRETRANSLATE_URL=http://my-translate:5000 # ... other variables Deploy\nKey Points:\nDatabase URL uses container name (cybercodeacademy-db), not localhost\nRedis URL uses container name (cybercodeacademy-redis)\nBackend can start without executor image (it will build it if missing)\nHealth check: / endpoint must return 200 OK\nResource 3: Frontend (Public Repository) # Type: Public Repository Purpose: Deploy the Next.js frontend independently Repository: mmornati/cyber-code-academyDockerfile: frontend/DockerfileBuild Context: / (repository root)\nConfiguration Steps:\nCreate a new Coolify resource\nSelect \u0026ldquo;Public Repository\u0026rdquo;\nConnect GitHub repository: mmornati/cyber-code-academy\nSet Dockerfile path: frontend/Dockerfile\nSet build context: / (repository root)\nEnable auto-redeploy on commits\nConfigure external network: cybercodeacademy-proxy\nSet environment variables:\nNEXT_PUBLIC_API_URL=https://api.yourdomain.com NEXT_PUBLIC_WS_URL=https://api.yourdomain.com NODE_ENV=production Configure Traefik routing (if using Coolify\u0026rsquo;s Traefik)\nDeploy\nKey Points:\nBuild context is repository root (needed for multi-stage build)\nAPI URL points to backend\u0026rsquo;s public domain\nFrontend waits for backend to be healthy before starting\nHealth check: GET / endpoint must return 200 OK\nNetwork Architecture Deep Dive # The network is the critical piece that makes everything work. Let me explain how we set it up.\nCreating the External Network\nFirst, we create the external network on the Coolify server:\ndocker network create cybercodeacademy-proxy This network exists independently of any Docker Compose file or Coolify resource. It\u0026rsquo;s persistent and shared.\nConnecting Services\nEach service connects to this network:\nInfrastructure (docker-compose.infra.yaml):\nnetworks: cybercodeacademy-proxy: external: true name: ${PROXY_NETWORK:-coolify} Backend (Coolify resource):\nIn Coolify\u0026rsquo;s network configuration, select \u0026ldquo;External Network\u0026rdquo;\nEnter network name: cybercodeacademy-proxy\nFrontend (Coolify resource):\nSame as backend: select \u0026ldquo;External Network\u0026rdquo;\nEnter network name: cybercodeacademy-proxy\nContainer Name Resolution\nOnce services are on the same network, Docker\u0026rsquo;s built-in DNS resolves container names to IP addresses:\ncybercodeacademy-db → PostgreSQL container IP\ncybercodeacademy-redis → Redis container IP\ncybercodeacademy-translate → LibreTranslate container IP\ncybercodeacademy-api → Backend container IP (if you need it)\nThis is why we use container names in connection strings instead of localhost or IP addresses.\nWhy External Networks Matter\nExternal networks allow:\nServices from different Docker Compose files to communicate\nServices deployed by different Coolify resources to communicate\nServices to find each other even after restarts (IPs change, names don\u0026rsquo;t)\nIndependent deployment without breaking connections\nWithout external networks, each Docker Compose file or Coolify resource would create its own isolated network, and services couldn\u0026rsquo;t communicate across resources.\nZero-Downtime Deployment: How It Works # Now for the exciting part: how we achieve zero-downtime deployments. The key is Coolify\u0026rsquo;s \u0026ldquo;Start-before-Stop\u0026rdquo; strategy combined with health checks.\nUnderstanding Start-before-Stop # Traditional deployments follow a \u0026ldquo;Stop-then-Start\u0026rdquo; pattern:\nStop old container\nBuild new container\nStart new container\nWait for health checks\nResult: Downtime during steps 1-4\nStart-before-Stop reverses this:\nBuild new container (in parallel with old one running)\nStart new container\nWait for health checks to pass\nSwitch traffic to new container\nStop old container\nResult: Zero downtime (old container serves traffic until new one is ready)\nBackend Update Process # Let\u0026rsquo;s walk through what happens when we update the backend:\nStep 1: Coolify Detects Change\nWe push a commit to the main branch\nCoolify\u0026rsquo;s webhook triggers a new deployment\nCoolify starts building the new backend container\nOld backend container continues serving traffic ✅\nStep 2: New Container Starts\nNew container is built with the latest code\nNew container starts on the cybercodeacademy-proxy network\nNew container can see infrastructure services (database, Redis)\nNew container begins initialization\nOld backend container still serving traffic ✅\nStep 3: Health Checks\nNew container runs its health check: curl -f http://localhost:8000/\nHealth check passes (container is ready)\nNew container is marked as \u0026ldquo;healthy\u0026rdquo;\nOld backend container still serving traffic ✅\nStep 4: Traffic Switch\nCoolify\u0026rsquo;s load balancer (Traefik) switches traffic to the new container\nNew container starts receiving requests\nOld container stops receiving new requests\nNo downtime ✅\nStep 5: Old Container Stops\nOld container is gracefully stopped\nConnections are closed\nOld container is removed\nNew container continues serving traffic ✅\nTotal Downtime: 0 seconds\nFrontend Update Process # The frontend follows the same pattern:\nStep 1: Build New Frontend\nCoolify builds new Next.js container\nBuild includes static asset generation\nOld frontend still serving pages ✅\nStep 2: Start New Container\nNew frontend container starts\nHealth check: wget --spider http://localhost:3000/\nOld frontend still serving pages ✅\nStep 3: Traffic Switch\nTraefik switches traffic to new frontend\nUsers see new version immediately\nNo downtime ✅\nStep 4: Stop Old Container\nOld container stops\nNew container continues serving ✅\nTotal Downtime: 0 seconds\nInfrastructure Stability # The beautiful part: infrastructure services never restart during backend or frontend deployments.\nDuring Backend Update:\nPostgreSQL: Running ✅\nRedis: Running ✅\nLibreTranslate: Running ✅\nBackend: Old → New (zero downtime) ✅\nDuring Frontend Update:\nPostgreSQL: Running ✅\nRedis: Running ✅\nLibreTranslate: Running ✅\nBackend: Running ✅\nFrontend: Old → New (zero downtime) ✅\nWhen Infrastructure Updates (rare):\nOnly infrastructure services restart\nBackend and frontend continue running (they reconnect automatically)\nMinimal impact (infrastructure updates are infrequent)\nWhy Health Checks Are Critical # Health checks are what make zero-downtime deployments possible. Without them, Coolify can\u0026rsquo;t know when a container is ready to accept traffic.\nBackend Health Check:\nHEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \\ CMD curl -f http://localhost:8000/ || exit 1 This checks:\nContainer is running\nApplication has started\nApplication is responding to HTTP requests\nDatabase connections are working (implicitly, since the app won\u0026rsquo;t start without DB)\nFrontend Health Check:\nHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1 This checks:\nContainer is running\nNext.js server has started\nPages are being served correctly\nWhat Happens If Health Checks Fail\nIf health checks fail, Coolify won\u0026rsquo;t switch traffic to the new container. The old container continues serving traffic, and you get a deployment failure notification. This is a safety mechanism—better to have a failed deployment than to serve broken code.\nImplementation Details # Now let\u0026rsquo;s get into the nitty-gritty of how we set everything up. I\u0026rsquo;ll walk you through each phase of the deployment process.\nPhase 1: Infrastructure Deployment # The infrastructure layer is the foundation, so we deploy it first.\nSetting Up the Docker Compose Resource # In Coolify:\nNavigate to \u0026ldquo;Resources\u0026rdquo;\nClick \u0026ldquo;New Resource\u0026rdquo;\nSelect \u0026ldquo;Docker Compose\u0026rdquo;\nName it: \u0026ldquo;Infrastructure\u0026rdquo; or \u0026ldquo;Database Stack\u0026rdquo;\nUploading the Compose File # Upload docker-compose.infra.yaml\nCoolify will parse the file and show all services\nVerify that all services are detected:\napp-db (PostgreSQL)\nredis (Redis)\nlibretranslate (LibreTranslate)\nexecutor-builder (Executor image builder)\nEnvironment Variables # Set these in Coolify\u0026rsquo;s environment variable section:\nPOSTGRES_USER=pguser POSTGRES_PASSWORD=\u0026lt;generate-strong-password\u0026gt; POSTGRES_DB=my-db PROXY_NETWORK=cybercodeacademy-proxy EXECUTOR_IMAGE_NAME=my-executor Security Note: Use a strong password for PostgreSQL. Generate one with:\nopenssl rand -base64 32 Network Configuration # Critical Step: Before deploying, create the external network:\n# SSH into your Coolify server docker network create cybercodeacademy-proxy Then, in Coolify\u0026rsquo;s network settings for this resource:\nSelect \u0026ldquo;External Network\u0026rdquo;\nEnter: cybercodeacademy-proxy\nVolume Management # The infrastructure uses Docker volumes for persistent data:\npostgres_data: PostgreSQL database files\nredis_data: Redis data (optional, Redis can be ephemeral)\nMigration Consideration: If you\u0026rsquo;re migrating from the old monolithic setup, you may need to reuse existing volumes. Check your old volumes:\ndocker volume ls | grep postgres docker volume ls | grep redis If you have existing volumes, you can reference them in docker-compose.infra.yaml:\nvolumes: postgres_data: external: true name: \u0026lt;existing-volume-name\u0026gt; Deploying # Click \u0026ldquo;Deploy\u0026rdquo; in Coolify\nWatch the logs to ensure all services start correctly\nVerify health checks pass:\nPostgreSQL: pg_isready should succeed\nRedis: redis-cli ping should return PONG\nLibreTranslate: HTTP check should succeed (may take 60-120 seconds)\nVerifying the Executor Image # After deployment, verify the executor image was built:\ndocker images | grep my-executor You should see: my-executor:latest\nIf it\u0026rsquo;s missing, the backend will build it automatically on startup, but it\u0026rsquo;s better to have it pre-built.\nPhase 2: Backend Deployment # Once infrastructure is running, we deploy the backend.\nCreating the Repository Resource # In Coolify:\nNavigate to \u0026ldquo;Resources\u0026rdquo;\nClick \u0026ldquo;New Resource\u0026rdquo;\nSelect \u0026ldquo;Public Repository\u0026rdquo;\nName it: \u0026ldquo;Backend API\u0026rdquo; or \u0026ldquo;FastAPI Backend\u0026rdquo;\nConnecting GitHub # Click \u0026ldquo;Connect Repository\u0026rdquo;\nAuthorize Coolify to access your GitHub account\nSelect repository: mmornati/cyber-code-academy\nSelect branch: main (or your production branch)\nDockerfile Configuration # Dockerfile Path: backend/Dockerfile\nBuild Context: /backend\nWhy /backend? The backend Dockerfile uses build arguments to handle different contexts:\nDevelopment: context is repository root (.), so it uses backend/ prefix\nProduction: context is /backend, so it uses empty prefix (.)\nThis allows the same Dockerfile to work in both scenarios.\nEnvironment Variables # Set these environment variables in Coolify:\n# Database Connection (uses container name, not localhost!) DATABASE_URL=postgresql+asyncpg://pguser:\u0026lt;password\u0026gt;@my-db:5432/my-db # Redis Connection (uses container name) REDIS_URL=redis://cybercodeacadem-redis:6379 # JWT Secrets (generate strong secrets) JWT_SECRET=\u0026lt;generate-strong-secret\u0026gt; JWT_REFRESH_SECRET=\u0026lt;generate-strong-secret\u0026gt; # Application Settings ENVIRONMENT=production ADMIN_EMAIL=admin@cybercodeacademy.dev ADMIN_PASSWORD=\u0026lt;secure-password\u0026gt; # Executor Configuration EXECUTOR_IMAGE_NAME=my-executor EXECUTOR_TIMEOUT_SECONDS=10 EXECUTOR_MEMORY_LIMIT=512m EXECUTOR_CPU_LIMIT=1.0 EXECUTOR_MAX_POOL_SIZE=5 # Docker Socket (for executor container management) DOCKER_HOST=unix:///var/run/docker.sock # AI Provider AI_PROVIDER=google GOOGLE_GENAI_API_KEY=\u0026lt;your-google-ai-key\u0026gt; # Translation Service (uses container name) LIBRETRANSLATE_URL=http://my-translate:5000 Critical Points:\nDATABASE_URL uses cybercodeacadem-db (container name), not localhost or an IP\nREDIS_URL uses cybercodeacadem-redis (container name)\nLIBRETRANSLATE_URL uses cybercodeacadem-translate (container name)\nAll secrets should be strong and unique\nNetwork Configuration # In Coolify\u0026rsquo;s network settings for the backend resource\nSelect \u0026ldquo;External Network\u0026rdquo;\nEnter: cybercodeacadem-proxy\nThis connects the backend to the same network as infrastructure services.\nDocker Socket Access # The backend needs access to the Docker socket to manage executor containers. In Coolify:\nEnable \u0026ldquo;Docker Socket\u0026rdquo; or \u0026ldquo;Privileged Mode\u0026rdquo;\nThis mounts /var/run/docker.sock into the container\nAllows the backend to create/stop executor containers for code execution\nSecurity Note: Docker socket access is powerful. Ensure your backend code is secure and doesn\u0026rsquo;t allow arbitrary container creation.\nAuto-Redeploy Configuration # Enable auto-redeploy:\nIn Coolify, go to the backend resource settings\nEnable \u0026ldquo;Auto Deploy on Push\u0026rdquo;\nSelect the branch: main (or your production branch)\nCoolify will automatically deploy when you push to this branch\nHealth Check Configuration # Coolify will use the health check defined in the Dockerfile:\nHEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \\ CMD curl -f http://localhost:8000/ || exit 1 Ensure your backend has a root endpoint (/) that returns 200 OK. This is what the health check calls.\nDeploying # Click \u0026ldquo;Deploy\u0026rdquo; in Coolify\nWatch the build logs\nOnce built, the container starts\nHealth checks run\nOnce healthy, the backend is ready\nVerifying Backend Connectivity # After deployment, verify the backend can connect to infrastructure:\n# Check backend logs docker logs cybercodeacadem-api # Look for: # - \u0026#34;Connected to database\u0026#34; # - \u0026#34;Redis connection established\u0026#34; # - \u0026#34;Application startup complete\u0026#34; If you see connection errors, check:\nNetwork configuration (all services on same network?)\nContainer names (match exactly?)\nEnvironment variables (correct passwords/secrets?)\nPhase 3: Frontend Deployment # Finally, we deploy the frontend.\nCreating the Repository Resource # Navigate to \u0026ldquo;Resources\u0026rdquo;\nClick \u0026ldquo;New Resource\u0026rdquo;\nSelect \u0026ldquo;Public Repository\u0026rdquo;\nName it: \u0026ldquo;Frontend Web\u0026rdquo; or \u0026ldquo;Next.js Frontend\u0026rdquo;\nConnecting GitHub # Same as backend:\nConnect repository: mmornati/cyber-code-academy\nSelect branch: main\nDockerfile Configuration # Dockerfile Path: frontend/Dockerfile\nBuild Context: / (repository root)\nWhy repository root? The frontend Dockerfile needs access to:\nfrontend/ directory (source code)\nuser-docs/ directory (documentation to build)\nRoot-level files if needed\nUsing repository root as build context allows the Dockerfile to copy from multiple directories.\nEnvironment Variables # Set these in Coolify:\nNEXT_PUBLIC_API_URL=https://api.yourdomain.com NEXT_PUBLIC_WS_URL=https://api.yourdomain.com NODE_ENV=production Important:\nNEXT_PUBLIC_* variables are embedded at build time, not runtime\nThey must be set before building the Docker image\nIf you change them, you must rebuild the frontend\nAPI URL Options:\nPublic Domain: https://api.yourdomain.com (if backend has public domain)\nInternal DNS: http://my-api:8000 (if using internal network, but this won\u0026rsquo;t work for browser requests)\nCoolify Proxy: Use Coolify\u0026rsquo;s internal proxy if configured\nFor browser requests, you typically need a public domain. The frontend runs in the user\u0026rsquo;s browser, so it can\u0026rsquo;t use Docker\u0026rsquo;s internal DNS.\nNetwork Configuration # Select \u0026ldquo;External Network\u0026rdquo;\nEnter: cybercodeacadem-proxy\nEven though the frontend doesn\u0026rsquo;t directly connect to infrastructure services, being on the same network can be useful for:\nHealth checks\nInternal monitoring\nFuture features that might need direct access\nTraefik Routing (Optional) # If using Coolify\u0026rsquo;s Traefik for routing:\nEnable \u0026ldquo;Traefik\u0026rdquo; in frontend resource settings\nSet domain: yourdomain.com\nTraefik will automatically:\nGenerate SSL certificates (Let\u0026rsquo;s Encrypt)\nRoute traffic to the frontend container\nHandle load balancing\nHealth Check # The frontend health check:\nHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1 Ensure your Next.js app has a /api/health endpoint that returns 200 OK.\nDeploying # Click \u0026ldquo;Deploy\u0026rdquo;\nBuild process may take 5-10 minutes (Next.js builds can be slow)\nOnce built, container starts\nHealth checks run\nFrontend is ready\nVerifying Frontend Connectivity # After deployment:\nOpen your domain in a browser\nCheck browser console for API errors\nVerify frontend can reach backend API\nTest a few key features (login, challenge loading, etc.)\nKey Configuration Details # Let me cover some important configuration details that apply to all services.\nContainer Naming Conventions # We use explicit container names for DNS resolution:\ncybercodeacadem-db (PostgreSQL)\ncybercodeacadem-redis (Redis)\ncybercodeacadem-translate (LibreTranslate)\ncybercodeacadem-api (Backend)\ncybercodeacadem-web (Frontend)\nWhy explicit names?\nPredictable DNS resolution\nEasy to reference in connection strings\nConsistent across deployments\nNo dependency on Docker Compose service names\nHealth Check Strategies # Infrastructure Services:\nPostgreSQL: pg_isready command\nRedis: redis-cli ping\nLibreTranslate: HTTP request to /\nApplication Services:\nBackend: HTTP GET to /\nFrontend: HTTP GET to /api/health\nBest Practices:\nHealth checks should be lightweight (fast)\nThey should verify the service is actually working, not just running\nUse appropriate intervals (30s is good for most services)\nSet reasonable timeouts (10s is usually enough)\nResource Limits # We set resource limits to prevent any single service from consuming all resources:\nPostgreSQL:\ndeploy: resources: limits: memory: 512M reservations: memory: 256M Redis:\ndeploy: resources: limits: memory: 256M reservations: memory: 64M Backend:\nMemory limit: 1G\nCPU limit: 2.0 (if needed)\nFrontend:\nMemory limit: 512M\nCPU limit: 1.0 (if needed)\nThese limits ensure fair resource allocation and prevent one service from starving others.\nBenefits \u0026amp; Results # Now that we\u0026rsquo;ve covered the implementation, let\u0026rsquo;s talk about the results. The migration has transformed how we deploy and operate our platform.\nOperational Benefits # Zero-Downtime Deployments The most obvious benefit: we can now deploy without any user-visible downtime. Frontend updates, backend updates, and even some infrastructure changes happen seamlessly. Users never see \u0026ldquo;Service Unavailable\u0026rdquo; errors during deployments.\nBefore: 3-5 minutes of downtime per deployment After: 0 seconds of downtime\nIndependent Scaling We can scale services independently based on their needs:\nFrontend: Scale up during peak traffic (more users browsing)\nBackend: Scale up during battle events (more API calls)\nInfrastructure: Keep stable (rarely needs scaling)\nThis wasn\u0026rsquo;t possible with the monolithic setup—we had to scale everything together.\nFaster Iteration Cycles Because deployments are risk-free (no downtime), we deploy more frequently:\nBefore: 1-2 deployments per week (batched changes)\nAfter: 5-10 deployments per week (deploy as soon as code is ready)\nThis means:\nBugs are fixed faster\nFeatures reach users sooner\nWe can experiment with confidence\nBetter Resource Utilization We\u0026rsquo;re no longer wasting resources restarting services that don\u0026rsquo;t need to restart:\nDatabase stays running (saves 30-60 seconds per deployment)\nRedis stays running (saves 10-20 seconds)\nLibreTranslate stays running (saves 60-120 seconds)\nOver a month, this adds up to significant time and resource savings.\nDeveloper Experience # Deploy Frontend Without Touching Database This is the game-changer. Frontend developers can now deploy UI changes without worrying about database restarts. A CSS fix? Deploy in 2 minutes, zero impact on backend or database.\nQuick Rollbacks If a deployment goes wrong, we can roll back just the affected service:\nFrontend broken? Roll back frontend only (30 seconds)\nBackend broken? Roll back backend only (1 minute)\nInfrastructure issue? Rare, but can be addressed independently\nWith the monolithic setup, any rollback required restarting everything (5+ minutes).\nParallel Development Different teams can work on different services without blocking each other:\nFrontend team deploys UI improvements\nBackend team deploys API changes\nBoth happen simultaneously, no conflicts\nConfidence in Deployments Knowing that deployments won\u0026rsquo;t cause downtime gives us confidence to:\nDeploy on Fridays (no more \u0026ldquo;no deployments on Fridays\u0026rdquo; rule)\nDeploy during business hours (users won\u0026rsquo;t notice)\nExperiment with new features (easy to roll back if needed)\nCost \u0026amp; Performance # Reduced Unnecessary Restarts Every unnecessary restart consumes:\nCPU cycles (container initialization)\nMemory (loading services into RAM)\nI/O bandwidth (reading files, connecting to databases)\nTime (waiting for services to start)\nBy eliminating unnecessary restarts, we:\nReduce server load\nLower resource costs\nImprove overall system stability\nBetter Resource Allocation With independent services, we can:\nAllocate more resources to services that need them\nScale down services that don\u0026rsquo;t need resources\nOptimize each service independently\nFor example:\nFrontend: Lightweight, can run on smaller instances\nBackend: More CPU-intensive, needs more resources\nDatabase: Memory-intensive, needs dedicated resources\nImproved Reliability The decoupled architecture is more resilient:\nIf frontend fails, backend and database keep running\nIf backend fails, database keeps running (data is safe)\nIf one service has issues, others are unaffected\nThis isolation prevents cascading failures.\nReal-World Example # Let me share a real example from our platform:\nScenario: We discovered a UI bug where the challenge browser wasn\u0026rsquo;t showing difficulty badges correctly. It was a simple CSS issue—one line of code.\nBefore (Monolithic):\nFix the CSS (5 minutes)\nWait until low-traffic period (2 hours later)\nDeploy (triggers full restart)\n4 minutes of downtime\nUsers see \u0026ldquo;Service Unavailable\u0026rdquo;\nTotal time: 2+ hours, 4 minutes of downtime\nAfter (Decoupled):\nFix the CSS (5 minutes)\nPush to main branch\nCoolify auto-deploys frontend (2 minutes)\nZero downtime\nUsers see the fix immediately\nTotal time: 7 minutes, 0 seconds of downtime\nThis is the difference decoupling makes.\nLessons Learned \u0026amp; Best Practices # After going through this migration, I\u0026rsquo;ve learned a lot. Here are the key lessons and best practices I\u0026rsquo;d recommend to anyone considering a similar migration.\nWhat Worked Well # 1. External Networks Are Your Friend Using an external network (cybercodeacadem-proxy) was the key to making everything work. It allows services from different Coolify resources to communicate seamlessly. Without it, we\u0026rsquo;d be stuck with complex networking workarounds.\n2. Container Names for DNS Using explicit container names (cybercodeacadem-db, cybercodeacadem-redis) instead of service names or IPs made connection strings predictable and reliable. Docker\u0026rsquo;s DNS resolution is rock-solid when you use container names.\n3. Health Checks Are Non-Negotiable Health checks are what make zero-downtime deployments possible. Without them, Coolify can\u0026rsquo;t know when a container is ready. Invest time in getting health checks right—they\u0026rsquo;re worth it.\n4. Build Context Matters Understanding Docker build contexts was crucial. The backend uses /backend as context, while the frontend uses / (repository root). Getting this wrong causes confusing build errors.\n5. Gradual Migration We didn\u0026rsquo;t migrate everything at once. We:\nSet up infrastructure first\nMigrated backend second\nMigrated frontend last\nThis gradual approach let us test each piece independently and catch issues early.\nChallenges Encountered # 1. Network Configuration Confusion Initially, we had issues with services not finding each other. The problem: we were mixing localhost, IP addresses, and container names. The solution: use container names consistently, and ensure all services are on the same external network.\n2. Environment Variable Timing Frontend environment variables (NEXT_PUBLIC_*) are embedded at build time, not runtime. We learned this the hard way when changing API URLs didn\u0026rsquo;t work until we rebuilt the image. The lesson: set environment variables before building, not after.\n3. Volume Migration Migrating existing PostgreSQL and Redis volumes was tricky. We had to:\nIdentify existing volumes\nReference them in the new compose file\nEnsure permissions were correct\nFor new deployments, this isn\u0026rsquo;t an issue, but for migrations, it\u0026rsquo;s something to plan for.\n4. Executor Image Building The executor image builder service in docker-compose.infra.yaml doesn\u0026rsquo;t run as a container—it only builds the image. Coolify initially didn\u0026rsquo;t build it automatically. We worked around this by having the backend build it on startup if missing, but it\u0026rsquo;s better to pre-build it.\n5. Health Check Endpoints Not all services had proper health check endpoints initially. We had to add:\nBackend: / endpoint that returns 200 OK\nFrontend: /api/health endpoint\nThis is easy to fix, but it\u0026rsquo;s something to plan for.\nRecommendations for Others # When to Use This Pattern\nThis decoupled architecture pattern is ideal when:\n✅ You have services with different update frequencies (stable infrastructure, frequently-changing applications)\n✅ You need zero-downtime deployments\n✅ You\u0026rsquo;re using a platform like Coolify that supports independent resource deployment\n✅ You have a monorepo with multiple applications\n✅ You want to scale services independently\nWhen NOT to Use This Pattern\nThis pattern might be overkill if:\n❌ You have a simple single-application setup\n❌ All your services change together (true monolith)\n❌ You don\u0026rsquo;t have deployment downtime issues\n❌ Your deployment platform doesn\u0026rsquo;t support independent resources\nNetwork Configuration Tips\nCreate the external network first: Before deploying anything, create the network:\ndocker network create \u0026lt;network-name\u0026gt; Use consistent naming: Use the same network name across all resources. We use cybercodeacademy-proxy everywhere.\nVerify network connectivity: After deploying, verify services can reach each other:\ndocker exec -it \u0026lt;container-name\u0026gt; ping \u0026lt;other-container-name\u0026gt; Document container names: Keep a list of container names and what they\u0026rsquo;re used for. This helps when configuring connection strings.\nHealth Check Best Practices\nMake health checks meaningful: Don\u0026rsquo;t just check if the process is running—check if the service is actually working. For example:\nDatabase: Can it accept connections?\nBackend: Can it respond to HTTP requests?\nFrontend: Can it serve pages?\nSet appropriate intervals:\nFast services (Redis): 10s interval\nMedium services (Backend): 30s interval\nSlow services (LibreTranslate): 30s interval with longer start period\nUse timeouts wisely: Health checks should fail fast if the service is broken, but give enough time for slow-starting services.\nTest health checks locally: Before deploying, test health checks in your local environment to ensure they work correctly.\nVolume Management Considerations\nPlan for volume migration: If you\u0026rsquo;re migrating from a monolithic setup, identify existing volumes and plan how to reference them.\nUse named volumes: Named volumes are easier to manage than anonymous volumes. They\u0026rsquo;re also easier to backup and migrate.\nBackup before migration: Always backup volumes before making changes. PostgreSQL and Redis data is critical—don\u0026rsquo;t risk losing it.\nConsider volume drivers: For production, consider using volume drivers (like NFS or cloud storage) for better reliability and portability.\nGeneral Best Practices\nStart with infrastructure: Deploy infrastructure services first. They\u0026rsquo;re the foundation, and other services depend on them.\nTest each layer independently: Don\u0026rsquo;t deploy everything at once. Test each layer (infrastructure, backend, frontend) independently before moving to the next.\nMonitor during migration: Watch logs, metrics, and health checks during migration. Catch issues early.\nHave a rollback plan: Know how to roll back each service independently. Practice rollbacks in a staging environment.\nDocument everything: Document container names, network names, environment variables, and connection strings. This helps when troubleshooting and onboarding new team members.\nUse version control: Keep all configuration files (Dockerfiles, docker-compose files) in version control. This makes it easy to track changes and roll back if needed.\nConclusion # Migrating from a monolithic Docker Compose deployment to a decoupled, three-tier architecture was one of the best decisions we made for Cyber Code Academy. The benefits are clear: zero-downtime deployments, independent scaling, faster iteration, and better resource utilization.\nThe journey wasn\u0026rsquo;t without challenges—network configuration, health checks, and volume migration required careful planning. But the result is a deployment system that\u0026rsquo;s robust, flexible, and professional.\nIf you\u0026rsquo;re facing similar deployment pain, I encourage you to consider this approach. Start small: separate your infrastructure from your applications. Then, as you gain confidence, further decouple your services. The investment in time and effort pays off in reduced downtime, faster deployments, and happier users.\nThe key takeaway: deployment architecture matters. A well-designed deployment system enables rapid iteration, confident releases, and reliable operations. Don\u0026rsquo;t let a monolithic deployment hold you back.\nFor us, this migration was transformative. We went from dreading deployments to deploying with confidence multiple times per week. Our users never see downtime, our developers can iterate quickly, and our platform is more resilient than ever.\nIf you\u0026rsquo;re interested in seeing the actual configuration files or have questions about the migration, check out our repository or reach out. I\u0026rsquo;m happy to share more details about our setup.\nHere\u0026rsquo;s to zero-downtime deployments! 🚀\nResources:\nCoolify Documentation\nDocker Networking Guide\nDocker Compose External Networks\n","date":"30 décembre 2025","externalUrl":null,"permalink":"/achieving-zero-downtime-deployments-on-coolify-a-journey-from-monolith-to-decoupled-architecture/","section":"Posts","summary":"","title":"Achieving Zero-Downtime Deployments on Coolify: A Journey from Monolith to Decoupled Architecture","type":"posts"},{"content":"","date":"30 décembre 2025","externalUrl":null,"permalink":"/tags/archi/","section":"Tags","summary":"","title":"Archi","type":"tags"},{"content":"","date":"30 décembre 2025","externalUrl":null,"permalink":"/tags/coolify/","section":"Tags","summary":"","title":"Coolify","type":"tags"},{"content":"","date":"30 décembre 2025","externalUrl":null,"permalink":"/tags/downtime/","section":"Tags","summary":"","title":"Downtime","type":"tags"},{"content":"My son just started learning Python at school here in France. Python! When I was his age, we were coding in Basic, then Pascal. Times have changed, and so has the way kids learn to code.\nThat got me thinking: what if I built something that could help him (and other teenagers) learn Python in a fun, interactive way? Something that feels more like a game than homework. So I did just that.\nWhat I Built # I created Cyber Code Academy - an interactive Python learning platform where students can solve challenges, compete in real-time battles, and learn through gamified experiences. It features:\n100+ Python challenges from beginner to expert level\nReal-time competitive battles against other learners\nAI-powered problem generation\nBuilt-in code editor with instant execution in the browser\nProgress tracking with XP, levels, and leaderboards\nAchievement badges and learning paths\nTech stack: Next.js 14, FastAPI, PostgreSQL, Redis, and Docker for isolated code execution.\nYou can try it out here: https://play.pygame.ovh/\nThe \u0026ldquo;Pure Vibe Coding\u0026rdquo; Approach # Here\u0026rsquo;s where it gets interesting. I built this entire project using what I call \u0026ldquo;pure vibe coding\u0026rdquo; - working entirely with Cursor and GitHub Copilot. No rigid planning, no extensive documentation upfront. Just flow, intuition, and AI assistance.\nThe result? A fully functional platform that went from idea to production faster than I ever thought possible (5 days). The AI tools didn\u0026rsquo;t just help with code - they accelerated every aspect of development: stack and libs choice, bug fixing, testing, \u0026hellip;\nBut here\u0026rsquo;s the kicker: even the documentation was fully AI-generated. Every page, every section, every explanation - and yes, even the screenshots - were created by AI. Check it out: https://play.pygame.ovh/docs/index.html\nIt\u0026rsquo;s a complete end-to-end AI-assisted development story: code, documentation, and visuals. In a future post, I\u0026rsquo;ll dive deeper into the actual workflow and methodology behind this approach (because let\u0026rsquo;s be honest, \u0026ldquo;pure vibe\u0026rdquo; sounds cool but there\u0026rsquo;s definitely a method to the madness).\nWho Is This For? # Primarily, I built this for teenagers like my son who are learning Python at school. But honestly? It\u0026rsquo;s for anyone who wants to learn or practice Python. It\u0026rsquo;s completely free, no strings attached.\nIt\u0026rsquo;s designed as a simple \u0026ldquo;game\u0026rdquo; to learn code interactively. No personal data collected - just a username (and who cares about that, right? :P). The focus is purely on learning and having fun while doing it.\nWhy Python? Because that\u0026rsquo;s what French schools are teaching now. No idea why this choice, but here we are. Python it is!\nA Security Experiment # One of the most fascinating things I noticed during development: security seemed to be auto-managed even when I didn\u0026rsquo;t explicitly specify it. The AI tools, combined with modern frameworks and best practices, naturally implemented security measures I hadn\u0026rsquo;t consciously planned for.\nThis got me curious. So here\u0026rsquo;s an open invitation to the skilled developers and security researchers out there: try to hack it. Seriously. Give it your best shot, and then tell me:\nHow you did it\nWhat happened\nWhat you found\nI\u0026rsquo;m genuinely interested in understanding how security emerged organically in this \u0026ldquo;pure vibe\u0026rdquo; approach. It\u0026rsquo;s a learning opportunity for all of us.\nWhat\u0026rsquo;s Next? # The code exists on GitHub, but I haven\u0026rsquo;t published it yet. I want to clean it up first, make it more presentable. But if there\u0026rsquo;s interest, I\u0026rsquo;m happy to share it. I\u0026rsquo;m also open to feedback.\nThis is meant to be a community project - a tool for learning, built with modern AI assistance, and open to improvement.\nTry It Out # Head over to https://play.pygame.ovh/ and give it a spin. Whether you\u0026rsquo;re a teenager learning Python, a developer looking to practice, or someone curious about what \u0026ldquo;pure vibe coding\u0026rdquo; can produce - you\u0026rsquo;re welcome.\nShare your feedback, report bugs, suggest features. And if you\u0026rsquo;re one of those skilled hackers I mentioned earlier? Well, you know what to do. 😉\nP.S. - For those interested in the technical details and workflow behind this \u0026ldquo;pure vibe coding\u0026rdquo; approach, stay tuned. I\u0026rsquo;ll be sharing a more detailed post on the methodology soon.\n","date":"28 décembre 2025","externalUrl":null,"permalink":"/building-cyber-code-academy-a-pure-vibe-coding-experiment/","section":"Posts","summary":"","title":"Building Cyber Code Academy: A \"Pure Vibe Coding\" Experiment","type":"posts"},{"content":"","date":"28 décembre 2025","externalUrl":null,"permalink":"/tags/vibe-coding/","section":"Tags","summary":"","title":"Vibe-Coding","type":"tags"},{"content":"","date":"26 janvier 2025","externalUrl":null,"permalink":"/tags/en/","section":"Tags","summary":"","title":"En","type":"tags"},{"content":"","date":"26 janvier 2025","externalUrl":null,"permalink":"/tags/homeassistant/","section":"Tags","summary":"","title":"Homeassistant","type":"tags"},{"content":"","date":"26 janvier 2025","externalUrl":null,"permalink":"/tags/integration/","section":"Tags","summary":"","title":"Integration","type":"tags"},{"content":"Home automation has transformed how we interact with our living spaces, offering unprecedented control, convenience, and efficiency. Today, I’m thrilled to introduce a custom integration that bridges the gap between Hitachi devices and HomeAssistant to allow better-integrated automations.\nWhy This Automation? # Hitachi recently significantly changed their approach to connecting wireless modules to their devices. These changes, while aimed at simplifying their ecosystem, introduced new challenges for smart home enthusiasts:\nSimplified Hardware: Hitachi transitioned from requiring two expensive modules for wireless connectivity to a single, more cost-effective module.\nLimited Connectivity Options: The new module is designed to connect exclusively through the official application and website, removing previously available options like APIs or Modbus.\nDiscovering the Solution: An analysis of how the official website communicates with the devices identified a list of URLs containing the necessary information to control the devices. This discovery became the foundation for building this custom automation.\nThis integration bridges the gap by leveraging these insights, enabling seamless control of Hitachi devices within the Home Assistant ecosystem.\nNOTE: As I own only a Hitachi Yutaki Head Pump, I don’t know how the integration can fit with the other brand devices.\nKey Features of the Integration # This custom integration, available on GitHub, brings powerful capabilities to your smart home setup, including:\nDevice Support: Seamlessly integrates with Hitachi appliances using the CS-Net communication protocol.\nReal-Time Monitoring: View status updates and diagnostics directly within Home Assistant.\nFull Control: Adjust device parameters such as temperature, mode, and power state remotely.\nAutomation-Ready: Leverage Home Assistant’s automation engine to create rules and triggers based on device activity.\nHow It Works # This integration leverages the CS-Net protocol to communicate with supported Hitachi devices. Once installed, it establishes a connection between Home Assistant and your appliances, enabling bidirectional communication for control and status updates. The setup process is straightforward and requires minimal technical expertise.\nIt uses the provided CSNet Home credentials to enable the communication and, when errors occur it re-authenticate the integration. There is so far not a better or proper way to interact with it.\nStep-by-Step Guide to Installation # Here’s how to get started:\nDownload the Integration: Add the integration repository to the HACS Custom repositories.\nInstall: Install the integration by looking for “csnet” or “hitachi” in the list of hacs available integration.\nRestart Home Assistant: Reload your instance to activate the integration.\nAdd the new Integration: going to the “Devices” section and add the new integration (looking with the same kind of filters used to find it in HACS)\nConfigure: The installation process will ask in the UI for your credentials and, everything goes fine it will display the found climate devices asking for their location.\nFor detailed steps, troubleshooting tips, and additional configuration options, refer to the documentation on GitHub.\nReal-World Use Cases # This integration opens the door to numerous possibilities:\nEnergy Savings: Automate your Hitachi air conditioner to maintain optimal temperatures during peak hours and reduce usage when not needed.\nComfort Automation: Pair your Hitachi devices with motion sensors to adjust settings based on room occupancy.\nUnified Ecosystem: Integrate your Hitachi appliances with other smart devices, such as thermostats, lights, and voice assistants, for seamless control.\nWhat’s Next? # This is just the beginning. Future updates will include:\nExpanded Device Support: Adding compatibility with more Hitachi products.\nCommunity Contributions: Welcoming feedback, bug reports, and feature requests from the Home Assistant community.\nConclusion # This custom integration for Home Assistant empowers users to unlock the full potential of their Hitachi devices, bringing them into the modern smart home ecosystem. With enhanced control, energy savings, and comfort at your fingertips, it’s time to take your home automation to the next level.\nReady to get started? Visit the GitHub repository to download the integration, and don’t forget to share your experience and feedback. Let’s build a smarter, more connected future together!\n","date":"26 janvier 2025","externalUrl":null,"permalink":"/seamlessly-automate-your-home-with-hitachi-devices-a-custom-home-assistant-integration/","section":"Posts","summary":"","title":"Seamlessly Automate Your Home with Hitachi Devices: A Custom Home Assistant Integration","type":"posts"},{"content":"Welcome to a detailed guide where I share my experience with one of the most challenging projects I’ve tackled using HomeAssistant: integrating the Everblue smart water meter. This guide will walk you through the entire process, step by step.\nIntroduction # In France, many homes are now equipped with the Everblue water meter. Water companies favor these devices because they facilitate remote readings, bypassing the need for physical access to properties, which can be challenging if homeowners are unavailable. What makes Everblue particularly interesting is its connectivity feature, which allows technicians to access water usage data wirelessly, avoiding incorrect billing due to missed readings. However, one must note that due to regulatory limits on wireless transmissions, these devices only operate during working hours on weekdays.\nWhy Integrate Everblue with Home Assistant? # As someone who enjoys automating every possible aspect of my home, integrating Everblue with Home Assistant allows me to monitor and control water usage meticulously. This setup helps answer questions like, \u0026ldquo;How much water does a shower use?\u0026rdquo; or \u0026ldquo;What’s the consumption when running the washing machine?\u0026rdquo; By incorporating Everblue into the Home Assistant energy dashboard, you can track these metrics over time, optimizing your water usage and understanding your consumption patterns. For this project, you will need:\nA Raspberry Pi (any model will do; I used an old RPi Rev B)\nA CC1101 wireless module, which operates at the 433Mhz frequency—commonly used in various household devices\nThis journey began with decrypting the communication protocol of the Everblue meter, thanks to a group of French enthusiasts who laid the groundwork. You can explore their original research here (note: the content is in French). Several subsequent projects have built on this, utilizing platforms like Raspberry Pi, ESP8266, and ESP32.\nStep-by-Step Integration Process # After experimenting with different versions, I settled on a Raspberry Pi-based fork that best suited my goals. The process is straightforward if you follow the instructions outlined in the GitHub project readme:\nEnable SPI via raspi-config.\nInstall WiringPi and libmosquitto-dev.\nConfigure meter and MQTT settings in the code.\nCompile and run the code to start receiving data.\nSet up a crontab to automate the reading process once a day.\nMake sure to adjust the device frequency as necessary, as slight deviations from the standard 433Mhz are possible. If the device is not initially detected, you may need to attempt multiple scans.\n./everblu_meters 0 If at the end of scan process the reported frequency is 0, this means device was not found. You may have to test several time before to have the device working frequency. When working you will have a message like the following one:\n{ \u0026#34;date\u0026#34;:\u0026#34;Sat Jul 15 13:06:04 2023\u0026#34;, \u0026#34;frequency\u0026#34;:\u0026#34;433.8000\u0026#34;, \u0026#34;min\u0026#34;:\u0026#34;433.7900\u0026#34;, \u0026#34;max\u0026#34;:\u0026#34;433.8100\u0026#34; } Once everything is well configured you can complete scheduling the EverBlue meter read once per day to prevent the device battery drain and, remember: working hours only!\nOn my side I create a simple crontab:\ncrontab -e With the following content:\n0 10 * * 1-5 /home/mmornati/everblu-meters-pi/everblu_meters 433.7560 \u0026gt;\u0026gt; /tmp/everblu.log 2\u0026gt;\u0026amp;1 This will be executed every week day at 10 a.m. and writing execution logs in the /tmp/everblu.log file let me check if everything is ok.\nThe file content\nCC1101 Verion : 0x0014 CC1101 found OK! Base MQTT topic is now everblu/cyble-23-0199454-pi Connected to MQTT broker (almost) Trying to query Cyble at 433.7560MHz Reading data...MQTT : Subscribed OK (mid: 1): 2 Consumption : 222583 Liters Battery left : 166 Months Read counter : 160 times Working hours : from 06H to 18H Local Time : Fri Apr 26 10:00:09 2024 RSSI / LQI : -48dBm / -128 CC1101 Verion : 0x0014 CC1101 found OK! Base MQTT topic is now everblu/cyble-23-0199454-pi Connected to MQTT broker (almost) Trying to query Cyble at 433.7560MHz Reading data...MQTT : Subscribed OK (mid: 1): 2 Consumption : 223486 Liters Battery left : 166 Months Read counter : 161 times Working hours : from 06H to 18H Local Time : Mon Apr 29 10:00:09 2024 RSSI / LQI : -48dBm / -128 The interesting thing in the information returned by the everblue meter you have the working hours of your device helping you for the schedule `from 06H to 18H`\nDisplaying Information in Home Assistant # The final step involves creating sensors within Home Assistant to display the data from the MQTT topics:\nsensor: - name: \u0026#34;water_meter_consumption\u0026#34; state_topic: \u0026#34;everblu/cyble-23-0199454-pi/json\u0026#34; unique_id: \u0026#34;water_meter_consumption\u0026#34; value_template: \u0026#34;{{ value_json.liters }}\u0026#34; unit_of_measurement: \u0026#34;L\u0026#34; device_class: water state_class: total_increasing - name: \u0026#34;water_meter_last_read\u0026#34; state_topic: \u0026#34;everblu/cyble-23-0199454-pi/json\u0026#34; unique_id: \u0026#34;water_meter_last_read\u0026#34; value_template: \u0026#34;{{ value_json.ts }}\u0026#34; device_class: timestamp These sensors will now appear in your Energy Dashboard, allowing you to monitor water usage effectively.\nCommon Issues # Occasionally, the script may fail to detect the Everblue meter. I’ve modified the script to retry several times before giving up, which resolves the issue most of the time. If problems persist, they\u0026rsquo;re usually resolved the following day.\nReplace the line 323 with the following code:\nint i=0; do { printf(\u0026#34;Reading data...\u0026#34;); meter_data = get_meter_data(); i++; sleep(5); } while (i\u0026lt;10 \u0026amp;\u0026amp; !meter_data.ok); Conclusion # While the journey to integrate the Everblue meter with Home Assistant was fraught with challenges, particularly with the initial ESP32 attempts, the final setup using a Raspberry Pi proved successful. This integration not only enhances my understanding of household water consumption but also demonstrates the power of home automation in managing resources efficiently.\nI hope this guide helps you streamline your own smart water meter integration. If you encounter any issues or have questions, feel free to reach out or comment below.\n","date":"30 avril 2024","externalUrl":null,"permalink":"/integrating-the-everblue-smart-water-meter-with-home-assistant/","section":"Posts","summary":"","title":"Integrating the Everblue Smart Water Meter with Home Assistant","type":"posts"},{"content":"","date":"30 avril 2024","externalUrl":null,"permalink":"/tags/water-meter/","section":"Tags","summary":"","title":"Water-Meter","type":"tags"},{"content":"","date":"2 janvier 2023","externalUrl":null,"permalink":"/tags/automation/","section":"Tags","summary":"","title":"Automation","type":"tags"},{"content":"Did you already move your harms to your motion sensor to power on your external light, for example when you are on your deck having dinner? It happened all the time to me and it is really frustrating\u0026hellip; so I created automation to stop it! 😎\nWhat do you need?\nA smart bulb (or equivalent to control a bulb)\nA smart motion sensor\nA input_boolean to check the way the light is powered on\nInput Boolean # Nothing special here, you just need to put it within your input_boolean.yaml file or directly in the configuration.yaml, depending on how you are managing your HassIO configuration.\nTo simplify my global configuration, on my side I put this within the global configuration file: input_boolean: !include components/input_boolean.yaml\ninput_boolean: !include components/input_boolean.yaml Which then allows you to put all your booleans configurations within the defined file.\nA different way to include external files, which I\u0026rsquo;m using with automation, is to put a folder instead of a file and ask Home Assistant to merge everything to get the final configuration:automation: !include_dir_merge_list automations/\nautomation: !include_dir_merge_list automations/ This allows your automation folder to put 1 YAML per automation and so separate it to simplify the management.\nAnyway, getting back to our boolean. What you have to put inside the external file is:\nterrasse_salon_auto_on: name: Terrasse Salon Motion ON icon: mdi:lightbulb This will create an input_boolean named terrasse_salon_auto_on we will use later in our automation.\nThe Automation # We have two different automation to control the power-on and the power-off.\n- alias: Terrasse Salon ON id: terrasse_salon_on trigger: platform: state entity_id: binary_sensor.motion_salon_occupancy to: \u0026#34;on\u0026#34; condition: - condition: state entity_id: light.terrasse_salon state: \u0026#34;off\u0026#34; - condition: numeric_state entity_id: sensor.motion_salon_illuminance_lux below: 50 - condition: state entity_id: input_boolean.terrasse_motion_sensor_enabled state: \u0026#34;on\u0026#34; action: - service: light.turn_on entity_id: light.terrasse_salon - service: input_boolean.turn_on entity_id: input_boolean.terrasse_salon_auto_on - alias: Terrasse Salon OFF id: terrasse_salon_off trigger: platform: state entity_id: binary_sensor.motion_salon_occupancy to: \u0026#34;off\u0026#34; for: minutes: 2 condition: - condition: state entity_id: light.terrasse_salon state: \u0026#34;on\u0026#34; - condition: or conditions: - condition: state entity_id: input_boolean.terrasse_salon_auto_on state: \u0026#34;on\u0026#34; - condition: state entity_id: input_boolean.ignore_light_manual_on state: \u0026#34;on\u0026#34; action: - service: light.turn_off entity_id: light.terrasse_salon - service: input_boolean.turn_off entity_id: input_boolean.terrasse_salon_auto_on As usual, we will enter each part of the script to understand what it does.\nThe Trigger # We want to turn on and off the light bulb when motion is detected. So we will use a state trigger on this particular sensor.\ntrigger: platform: state entity_id: binary_sensor.motion_salon_occupancy to: \u0026#34;on\u0026#34; When the occupancy sensor of the motion sensor is moving to on the script is triggered.\nFor the off part, we improve a little bit the trigger to prevent the light from flickering all the time if we are outside but not always moving or not always in front of the motion sensor.\ntrigger: platform: state entity_id: binary_sensor.motion_salon_occupancy to: \u0026#34;off\u0026#34; for: minutes: 2 The for minutes is doing the job: if the occupancy is off for at least 2 minutes, the action is triggered.\nThe Conditions # If there is motion, when do we want to power on the light? If it is dark and if, for sure, the light is off. So, this is mainly what we find:\ncondition: - condition: state entity_id: light.terrasse_salon state: \u0026#34;off\u0026#34; - condition: numeric_state entity_id: sensor.motion_salon_illuminance_lux below: 50 - condition: state entity_id: input_boolean.terrasse_motion_sensor_enabled state: \u0026#34;on\u0026#34; The state part is checking if the light is off\nThe numeric_state is validated by the illuminance value provided by the motion sensor. Which value to put here? Just made some tests. 0 should be a good value (no light at all) but I preferred to move a little bit up to have the power bulb powered on with low illuminance.\nThe last state is another input_boolean I added to be able to completely prevent light from being powered on. I\u0026rsquo;m using this during the night: if the night alarm is on, this means nobody will go outside, so I don\u0026rsquo;t want to have the lights powered on by movements.\nFor the power-off action, there is something similar, but it is here we will use the added input boolean to do the magic.\ncondition: - condition: state entity_id: light.terrasse_salon state: \u0026#34;on\u0026#34; - condition: or conditions: - condition: state entity_id: input_boolean.terrasse_salon_auto_on state: \u0026#34;on\u0026#34; - condition: state entity_id: input_boolean.ignore_light_manual_on state: \u0026#34;on\u0026#34; The state of the light. It sure must be on\nThe state input_boolean we previously configured. We will power off the light bulb if it was automatically turned on (we will see in a while when this flag will be turned on). This means if we power on the light with the home assistant application or if a switch, the flag should be false and the light won\u0026rsquo;t be turned off.\nHere you will see a or condition with a second input_boolean.ignore_light_manual_on. I\u0026rsquo;m using it to disable the previous flag: If I want to turn off anyway the light, never mind how it was turned on.\nThe Action # If everything is validated the light should be turned on or off, depending on the automation we are considering, but not only: we will control the input_boolean at this level.\naction: - service: light.turn_on entity_id: light.terrasse_salon - service: input_boolean.turn_on entity_id: input_boolean.terrasse_salon_auto_on You can see in the turn-on script, two services are fired: one for the light itself and the second one to move the boolean to true. This means if the light is turned on by the automation, the boolean contains the value to check this.\nIt is in my opinion the simple way to control this, but you can check in many other ways.\nOn the power-off part, it is exactly the opposite: we move the flag to false to get back to the initial state.\naction: - service: light.turn_off entity_id: light.terrasse_salon - service: input_boolean.turn_off entity_id: input_boolean.terrasse_salon_auto_on ","date":"2 janvier 2023","externalUrl":null,"permalink":"/home-assistant-motion-sensor-coupled-with-a-switch/","section":"Posts","summary":"","title":"Home Assistant: motion sensor coupled with a switch","type":"posts"},{"content":"","date":"2 janvier 2023","externalUrl":null,"permalink":"/tags/motion-sensor/","section":"Tags","summary":"","title":"Motion-Sensor","type":"tags"},{"content":"","date":"1 janvier 2023","externalUrl":null,"permalink":"/tags/cover/","section":"Tags","summary":"","title":"Cover","type":"tags"},{"content":"Today I will show you a simple script to help increase your home\u0026rsquo;s energetic performance by regulating the internal temperature base on the external values.\nIt is the first simpler version based on a single temperature point but I have a newer one ready to be tested but I need to wait for hotter days 😅\nWhat do you need for this?\n* Automatic / Home Assistant controller Covers\n* One (or more) temperature sensors\n* and for sure, one or more windows exposed to the sunlight 😉\nThe Trigger # As I already described for the presence simulation script, the trigger is a time_pattern because I want to constantly recheck during a specific time frame if conditions are met.\nAn alternative, to reduce the number of execution, is to use a Multi Trigger: when one of the triggers is validated, the automation is started. I will see at the end of the blog article how we can change automation in this way.\ntrigger: - platform: time_pattern minutes: \u0026#34;/5\u0026#34; Automation is started every 5 minutes\nThe Conditions # Here we will find a lot of tests to be sure we are closing at the right moment.\ncondition: - condition: time alias: \u0026#34;Time 13~20\u0026#34; after: \u0026#34;12:30:00\u0026#34; before: \u0026#34;18:00:00\u0026#34; - condition: or conditions: - condition: template # If automation was never triggered value_template: \u0026#34;{{ states.automation.close_cover_based_on_afternoon_temperature.attributes.last_triggered == none }}\u0026#34; - condition: template # If automation not played in the last 8 hours (means played only the day before) value_template: \u0026#34;{{ ( as_timestamp(now()) - as_timestamp(state_attr(\u0026#39;automation.close_cover_based_on_afternoon_temperature\u0026#39;, \u0026#39;last_triggered\u0026#39;)) |int(0)) \u0026gt; 28800 }}\u0026#34; - condition: template value_template: \u0026#34;{{ states.sensor.netatmo_maison_willems_indoor_namodule1_temperature.state|float \u0026gt; states.sensor.capteur_mouvement_salon_temperature.state|float + 2 }}\u0026#34; - condition: numeric_state entity_id: sensor.capteur_mouvement_salon_temperature above: 20 Time\nThe window covers I want to control are south-exposed, for this reason, I\u0026rsquo;m going to execute the automation only during the afternoon when the sun is completely facing the windows: after: \u0026quot;12:30:00\u0026quot; before: \u0026quot;18:00:00\u0026quot;\nNot executed already\n- condition: or conditions: - condition: template # If automation was never triggered value_template: \u0026#34;{{ states.automation.close_cover_based_on_afternoon_temperature.attributes.last_triggered == none }}\u0026#34; - condition: template # If automation not played in the last 8 hours (means played only the day before) value_template: \u0026#34;{{ ( as_timestamp(now()) - as_timestamp(state_attr(\u0026#39;automation.close_cover_based_on_afternoon_temperature\u0026#39;, \u0026#39;last_triggered\u0026#39;)) |int(0)) \u0026gt; 28800 }}\u0026#34; This part of the script, which seems hard to understand I know, is used to check if the automation was already fired (until the execution) or never executed at all (necessary for the first execution or if the last time was long away that historic data are removed).\nFor this check, we use the last_triggered property on the automation itself, checking if it none or if the last execution was fired more than 8 hours before. Why 8 hours? Never mind, in the end, you just need to put here a value preventing the execution in the same timeframe (12h30 to 18h) and allowing the execution the day after (18h to 12h30). The value 8 is covering both 2 cases: greater than 6:30 hours (18-12h30) and less than 18:30 hours (12h30 - 18h).\nTemperature\n- condition: template value_template: \u0026#34;{{ states.sensor.netatmo_maison_willems_indoor_namodule1_temperature.state|float \u0026gt; states.sensor.capteur_mouvement_salon_temperature.state|float + 2 }}\u0026#34; - condition: numeric_state entity_id: sensor.capteur_mouvement_salon_temperature above: 20 The 2 other conditions are checking the internal and external temperature.\nFor the internal, second condition, I\u0026rsquo;m checking only the temperature sensor in the room where I control the covers and it must be above 20 degrees to trigger the automation.\nThe other condition is checking the difference between the internal and the external temperature: the external must be at least 2 degrees greater than the internal.\n* states.sensor.netatmo_maison_willems_indoor_namodule1_temperature.state|float external module\n* states.sensor.capteur_movement_salon_temperature.state|float + 2 internal module + 2 degrees.\nThe Action # If everything is validated, the covers are closed.\naction: - service: cover.set_cover_position data: entity_id: - cover.salon_n1_6 - cover.salon_n2 - cover.salon_n3_12 - cover.salon_n4_14 - cover.chambre_jardin_3 position: 40 All the covers I want to control are placed at 40%. Not completely closed, but it is enough to reduce the light entering the room.\nI added an additional cover in a separate room without creating another action. It is only to simplify the management as the exposure is the same.\nIf we put it all together the script is the following:\n- id: cover_closes_weather alias: \u0026#34;Close cover based on afternoon temperature\u0026#34; trigger: - platform: time_pattern minutes: \u0026#34;/5\u0026#34; condition: - condition: time alias: \u0026#34;Time 13~20\u0026#34; after: \u0026#34;12:30:00\u0026#34; before: \u0026#34;18:00:00\u0026#34; - condition: or conditions: - condition: template # If automation was never triggered value_template: \u0026#34;{{ states.automation.close_cover_based_on_afternoon_temperature.attributes.last_triggered == none }}\u0026#34; - condition: template # If automation not played in the last 8 hours (means played only the day before) value_template: \u0026#34;{{ ( as_timestamp(now()) - as_timestamp(state_attr(\u0026#39;automation.close_cover_based_on_afternoon_temperature\u0026#39;, \u0026#39;last_triggered\u0026#39;)) |int(0)) \u0026gt; 28800 }}\u0026#34; - condition: template value_template: \u0026#34;{{ states.sensor.netatmo_maison_willems_indoor_namodule1_temperature.state|float \u0026gt; states.sensor.capteur_mouvement_salon_temperature.state|float + 2 }}\u0026#34; - condition: numeric_state entity_id: sensor.capteur_mouvement_salon_temperature above: 20 action: - service: cover.set_cover_position data: entity_id: - cover.salon_n1_6 - cover.salon_n2 - cover.salon_n3_12 - cover.salon_n4_14 - cover.chambre_jardin_3 position: 40 I used it for the last 2 years and there is a big difference in the temperature feeling you have when the covers are closed. So it is a big game changer for me.\nDifferent triggers to reduce the number of execution # As I said at the beginning, we can find a different way to manage the trigger, instead of the simple time_pattern. This will contribute to reducing the number of executions: even if the action is not fired, we are entering in the conditions check, using a little bit of your CPU.\nIf we get back our automation, the real information we need to trigger the automation is the temperature: external is more than 2 degrees greater than internal and internal is above 20 degrees.\nAn example of what we can change:\nautomation: trigger: - platform: template value_template: \u0026#34;{{ states.sensor.netatmo_maison_willems_indoor_namodule1_temperature.state|float \u0026gt; states.sensor.capteur_mouvement_salon_temperature.state|float + 2 }}\u0026#34; for: minutes: 5 In this way, we are triggering based on the external vs internal condition, and we check if the value remains true for at least 5 minutes.\nWe could add a second trigger about the internal temperature only, but we have to keep in mind that the trigger is evaluated with an or condition: if at least one is true, the action script is executed (but maybe conditions prevent it to be fired).\n- platform: numeric_state entity_id: sensor.capteur_mouvement_salon_temperature above: 20 It is up to you to define what is the best trigger in your situation, and what you are ready to accept in terms of the number of \u0026ldquo;false\u0026rdquo; executions.\nFuture enhancement # This is the version I used so far but the action was \u0026ldquo;wrongly\u0026rdquo; fired sometime. As it is based only on temperature, in the summer all the conditions can be valid even if it is rainy outside. With these weather conditions, the internal temperature is not increasing because the sun is not going through the windows.\nAt the end of summer, I installed some new external motion sensors I can use to add a new parameter: light intensity.\nWhat I added is a test about the lux parameter, which I\u0026rsquo;m already using to trigger the external spots.\nBut this is another story 😎\n","date":"1 janvier 2023","externalUrl":null,"permalink":"/homeassistant-close-cover-to-control-the-home-temperature/","section":"Posts","summary":"","title":"HomeAssistant: Close cover to control the home temperature","type":"posts"},{"content":"","date":"1 janvier 2023","externalUrl":null,"permalink":"/tags/weather/","section":"Tags","summary":"","title":"Weather","type":"tags"},{"content":"In this blog post, I will show how you can use a ZigBee in a completely different and unusual way.\nYou can control devices using a different protocol (ex covers using ZWave) but also, and I find it much more important, to use the different buttons on the same controller to drive different lights/devices or do a different action based on the number of click within a short period.\nDo you know the Philips Hue Switch? When you are simply binding it to lights, all the buttons get a specific function over those specific lights: toggle, increase or decrease intensity, and play a scenario. But I never used some of those buttons.\nI\u0026rsquo;m going to describe everything we will see in this blog post using my actual configuration with Zigbee2MQTT. But by changing a little bit the event part, what we will see can be adapted to any ZigBee add-on.\nThe Direct Binding # The standard way to configure a button/switch is by binding it to a light or a group of lights within the addon.\nThere is a huge advantage to doing this: the button and the light(s) are hardly connected, which means they can interact directly even without the home assistant or the zigbee2mqtt. In this way, never mind if you are restarting/upgrading/\u0026hellip; your home assistant, the lights can be powered on and off using the physical button configured for this.\nThe side effect is that, at least within the Zigbee2MQTT add-on, you can do only a simple bind: the switch/button will do what is intended to do by default.\nButton events # A different way to use buttons or switches is by detecting the generated events and then automating things over them.\nBut I started saying the side effects of this: events just go to Home Assistant if your ZigBee automation is started and home assistant can execute the automation only if the core is up too. So you can have some buttons going offline during \u0026ldquo;maintenance operation\u0026rdquo;. It is for me an important point because can be a pain point for your family if you are geeking a lot with your home assistant 😅😱\nWhat events are produced by my switch # Each button/switch produces all the time an event that is sent to Home Assistant. This means you have nothing to do more within your configuration to move from binding to event configuration.\nBut, what are the events produced by your button? It depends on the button you are using and sometimes even the brand can change the way events are generated.\nTo discover this event you can start a listener in the developer tools section of the home assistant, and, as I said at the beginning the event depends on your add-on: for Deconz is deconz_event, for ZHA is zha_event, \u0026hellip; And what about ZigBee2MQTT? The difference is that events are sent as messages on the message broker. So, instead of listening to an event, you can listen to a message topic. 😎\nI\u0026rsquo;m doing this directly on the terminal. Do not know if there is a different way for this, but it is not too complex this way.\nmosquitto_sub -h 127.0.0.1 -v -t \u0026#34;zigbee2mqtt/switch_entree\u0026#34; The important part is in the -t parameter (the topic): you have to put the name of your device after the zigbee2mqtt.\nWARNING: in the latest version of mosquito it seems you can\u0026rsquo;t log in without strong authentication. You should then add to the previous command the -u and -P parameters to provide the username and password.\nOnce the script is executed, if an event of the device you want to check comes up, you will see it on the screen.\nIn the screenshot, you have all the action related to the Philips Hue Switch (the first generation): on_press, up_press, up_press_release, \u0026hellip;\nYou can then use the desired ones within your automation, and even add other parameters coming in each event to trigger your action differently.\nIf following these steps is not allowing you to see the events, you can change the topic or listen for all the events coming from ZigBee2MQTT. For this just use the wildcard as the topic name: -t \u0026quot;zigbee2mqtt/#\u0026quot;\nIn this way, you will see a lot of events if you have a big network!\nThe Automation # Now we have all the information we need, we can set all up in the automation to control what we want.\nI provide here an example:\n- alias: Switch Toggle Entree id: switch_toggle_entree trigger: platform: mqtt topic: \u0026#34;zigbee2mqtt/switch_entree_02\u0026#34; condition: condition: template value_template: \u0026#39;{{ \u0026#34;on_press\u0026#34; == trigger.payload_json.action }}\u0026#39; action: entity_id: light.entree service: light.toggle - alias: Switch Toggle Escalier id: switch_toggle_escalier trigger: platform: mqtt topic: \u0026#34;zigbee2mqtt/switch_entree_02\u0026#34; condition: condition: template value_template: \u0026#39;{{ \u0026#34;off_press\u0026#34; == trigger.payload_json.action }}\u0026#39; action: entity_id: light.escalier service: light.toggle The switch\u0026rsquo;s upper button toggles a light, and the \u0026ldquo;off button\u0026rdquo; another one. The toggle service is just changing the state based on the actual one: if powered on, the light will be switched off; and the opposite.\nBut, as we are within automation, there is a world opened for us 😎 We can change the light intensity based on the hour of the day: if it is after midnight but before 7 AM the lith is powered on at 30%, 100% all other hours.\nHere is where you have to consider if the side effect can have a huge impact compared to what you gain entering the automation scripts.\n","date":"31 décembre 2022","externalUrl":null,"permalink":"/home-assistant-use-zigbee-buttons-to-control-other-protocol-devices/","section":"Posts","summary":"","title":"Home Assistant: use ZigBee buttons to control other protocol devices","type":"posts"},{"content":"","date":"31 décembre 2022","externalUrl":null,"permalink":"/tags/mosquitto/","section":"Tags","summary":"","title":"Mosquitto","type":"tags"},{"content":"","date":"31 décembre 2022","externalUrl":null,"permalink":"/tags/zigbee/","section":"Tags","summary":"","title":"Zigbee","type":"tags"},{"content":"","date":"31 décembre 2022","externalUrl":null,"permalink":"/tags/zigbee2mqtt/","section":"Tags","summary":"","title":"Zigbee2mqtt","type":"tags"},{"content":"Do you remember the \u0026ldquo;Home Alone\u0026rdquo; movie? When Kevin simulate the presence of his family at home using lights, television sounds, persons moving in the living room, \u0026hellip;?\nYou can do the same using your Smart Home devices and Home Assistant.\nWhat to control depends on the connected devices you have but there are infinite possibilities: start the light randomly, play music if presence is detected somewhere around the home, \u0026hellip; I show here a simple script controlled by automation to start a random light at a random hour.\nThe script # Add the following script in your scripts configuration file (ie scripts.yaml)\nlight_duration: mode: parallel description: \u0026#34;Turns on a light for a while, and then turns it off\u0026#34; fields: light: description: \u0026#34;A specific light\u0026#34; example: \u0026#34;light.bedroom\u0026#34; duration: description: \u0026#34;How long the light should be on in minutes\u0026#34; example: \u0026#34;25\u0026#34; sequence: - service: homeassistant.turn_on data: entity_id: \u0026#34;{{ light }}\u0026#34; - delay: \u0026#34;{{ duration }}\u0026#34; - service: homeassistant.turn_off data: entity_id: \u0026#34;{{ light }}\u0026#34; I found it somewhere on the net a while ago, but I don\u0026rsquo;t remember exactly where (so, sorry about the missing reference if you are the original writer of the script 😅).\nThe script is executing sequentially the turn_on, delay and turn_off, each of these steps is getting a variable: the light (or it can be any device with ON/OFF mode) to control and the global duration.\nThe parallel at the beginning allows several lights to be started in the same timeframe.\nIf you use a different mode, a previously started script can be killed and so the lights will never be turned off. I preferred the parallel to have an even more random simulation.\nThe automation # The automation will then start the script providing the correct parameters.\n- id: random_away_lights alias: \u0026#34;Random Away Lights\u0026#34; mode: parallel trigger: - platform: time_pattern minutes: \u0026#34;/30\u0026#34; condition: - condition: state entity_id: input_boolean.away state: \u0026#34;on\u0026#34; - condition: sun after: sunset after_offset: \u0026#34;-00:30:00\u0026#34; - condition: time before: \u0026#34;23:59:00\u0026#34; action: service: script.light_duration data: light: \u0026#34;{{states.group.simulation_lights.attributes.entity_id | random}}\u0026#34; duration: \u0026#34;00:{{ \u0026#39;{:02}\u0026#39;.format(range(5,30) | random | int) }}:00\u0026#34; The trigger I\u0026rsquo;m using a simple time_pattern: the automation is started every 30 minutes. I preferred this to a specific time or event because we can be outside the home even after the specific chosen event. To understand this, imagine you decide to start the automation at 20h and then, in the condition you have to add a check \u0026ldquo;if I\u0026rsquo;m not at home\u0026rdquo;. If this check is false the automation stop without any execution. But, if you leave the home at 20h05 it will never be triggered again. To fix this you can create a second automation linked to the \u0026ldquo;going out event\u0026rdquo; but personally I find it easy to understand with a simple time pattern. There is a code executed every 30 minutes, but in the end, home assistant is mainly doing nothing.\nThe condition is a group of checks. It is executed only if:\nthe away boolean is true. In my case, the boolean is set to true when I set the alarm in \u0026ldquo;away mode\u0026rdquo;.\nWe are after the sunset, or better 30 minutes before the sunset using the offset I put. So I simulate the presence only if it is dark outside\nuntil a specific hour. The script goes ahead doing stuff until 23h59.\nThe action is where we are then doing the magic: the previous configuration script is executed with the two variables (light and duration) filled dynamically up.\nThe light choice is made using\n{{states.group.simulation_lights.attributes.entity_id | random}}\nI created a group with a list of lights I want to use to simulate the presence. I put only the lights within the rooms visible from the outside.\nsimulation_lights: name: Lights Presence Simulation entities: - light.salle_manger - light.cuisine_table - light.bureau_marco - light.salon_corner The random function is used to select randomly 😎 within the provided list. The result is the entity ID to use.\nThe duration is selected in a similar way with\n\u0026quot;00:{{ '{:02}'.format(range(5,30) | random | int) }}:00\u0026quot;\nThe final result is a string like 00:10:00, so we have the number of minutes the light must be kept on.\nTo understand the script:\n'{:02}' is giving the number of digits of the final \u0026ldquo;number\u0026rdquo;. Here we are saying that the format must always be a two digits string. 5 will be 05. If we have a different format the delay procedure in the script will fail with an error.\nrange(5,30) says we want any number between 5 and 30 (minutes).\nrandom nothing to add I think\nint is to convert the result as a number without a decimal.\nIf we put it all together the script can be read as the following: select a random number between 5 and 30, converted it into an integer, and then formatted as 2 digit string.\nIf we get the whole automation at once, every 30 minutes the automation is started and if the conditions are met, a light within the defined group is selected and turned on for a random time between 5 and 30.\nYou can change any of the parameters I described, to adapt everything to your particular case.\n","date":"30 décembre 2022","externalUrl":null,"permalink":"/home-assistant-simple-presence-simulation-script/","section":"Posts","summary":"","title":"Home Assistant: simple \"presence simulation\" script","type":"posts"},{"content":"","date":"30 décembre 2022","externalUrl":null,"permalink":"/tags/light/","section":"Tags","summary":"","title":"Light","type":"tags"},{"content":"","date":"30 décembre 2022","externalUrl":null,"permalink":"/tags/script/","section":"Tags","summary":"","title":"Script","type":"tags"},{"content":"","date":"29 décembre 2022","externalUrl":null,"permalink":"/tags/google-calendar/","section":"Tags","summary":"","title":"Google-Calendar","type":"tags"},{"content":"Automations in Home Assistant are very powerful allowing us to control anything in our home and, in this way, help reduce cost and consumption.\nTo simplify the management I decided to use events to trigger input_boolean flags, and then use the boolean value to trigger what was needed. In this case, if I want to add events I don\u0026rsquo;t need to change a lot of automation because the boolean value is making an abstraction layer.\nHolidays Flag # I\u0026rsquo;ll try to drive you to my solution using an example. What about if you want to adapt your home automation on holiday? Following what I said just before, I added a holidays flag\nThis flag is then used in automation I want to change when I\u0026rsquo;m not at home for a \u0026ldquo;long period\u0026rdquo;. For example the water heater:\nIt is started during the night but only if the holiday flag is off.\nControl the flag # And now, how to know if I\u0026rsquo;m on holiday or not? A simple way to control it, as it is a flag, is switching it manually. But, in 2022, who is still doing manual things? 😂\nIn Home Assistant there is a Google Calendar integration that allows you to download all the events from one, or more, calendars in a google account. Each of these events became a home assistant event\u0026hellip; and then the rest is everything you already know 😎\nWhen installing the integration you can add it a read or read/write access to the calendars. In some cases, you would like to create new events from home assistant (Did not use it on my side already).\nOnce connected you can see the list of calendars you are managing in your google account, each of them is giving an entity in home assistant\nOh, what\u0026rsquo;s that homeautomation calendar? 🤩 I created it to separate my personal events, from the ones I would like to use to control the automation. But it is not necessary to do it in this way.\nFrom now on, you can create contition or trigger in automation script based on what happens on the calendar entity.\n- alias: Calendar Holidays Event id: calendar_holidays_event trigger: - platform: calendar event: start entity_id: calendar.homeautomation - platform: calendar event: end entity_id: calendar.homeautomation condition: - condition: template value_template: \u0026#34;{{ \u0026#39;Holidays\u0026#39; in trigger.calendar_event.summary }}\u0026#34; action: - if: - \u0026#34;{{ trigger.event == \u0026#39;start\u0026#39; }}\u0026#34; then: - service: input_boolean.turn_on entity_id: input_boolean.vacation else: - service: input_boolean.turn_off entity_id: input_boolean.vacation mode: queued In this example, the automation is triggered by the start and end calendar events, but filtered only for events with a title starting with Holidays.\nCreate your calendar event, and then let the home automate 😎\nOnce synchronized with Home Assistant, the calendar.homeautomation entity will give you the information about the first detected event:\nmessage: Holidays all_day: true start_time: \u0026#39;2023-02-11 00:00:00\u0026#39; end_time: \u0026#39;2023-02-18 00:00:00\u0026#39; location: \u0026#39;\u0026#39; description: \u0026#39;\u0026#39; offset_reached: false friendly_name: Homeautomation As I have a separate calendar to control the automation, the holidays putting in this one can be different from the real holidays days. In the example with the water heater, I would like to start back all the automation the day before I come back home.\n","date":"29 décembre 2022","externalUrl":null,"permalink":"/home-assistant-control-automation-with-google-calendar/","section":"Posts","summary":"","title":"Home Assistant: control automation with Google Calendar","type":"posts"},{"content":"","date":"28 novembre 2022","externalUrl":null,"permalink":"/tags/google-wifi/","section":"Tags","summary":"","title":"Google-Wifi","type":"tags"},{"content":"","date":"28 novembre 2022","externalUrl":null,"permalink":"/tags/openvpn/","section":"Tags","summary":"","title":"Openvpn","type":"tags"},{"content":"In 2022 the OpenWrt community released a version compatible with Google WiFi devices: https://openwrt.org/toh/google/wifi It is not possible to get out from the default Google firmware and benefit of a quite good device, adding a lot of functionalities!\nI will not go through how you can configure it within this blog post, as you can find a ton of tutorials online to make the configuration. But I will be, for sure available if you have any problem.\nOpenVPN # One of the good features I love on OpenWRT is the way to secure all my network forcing the usage of a VPN.\nFor this, you just have to configure the OpenVPN client by uploading the related ovpn file (or configuring everything manually), and then, by default, once connected the whole WiFi traffic is forwarded to the tun0 device. If you have a good VPN (allowing a decent bandwidth) you don\u0026rsquo;t have any other setting for this part. If, like me, the global VPN speed is not always so good (~20Mbit/sec vs 300 😱), never mind the server I\u0026rsquo;m using, you may want to select which devices you want to redirect to the VPN and which other you don\u0026rsquo;t want.\nConfiguring to now use VPN by default # If like me you prefer to select only which devices you want to protect and which others you want to leave \u0026ldquo;as usual\u0026rdquo; you can change the OpenVPN configuration to not push the tun0 as the default gateway. For this you just have to add within your OpenVPN configuration file, the following option:\npull-filter ignore redirect-gateway You should have something like this\nRestarting the OpenVPN service on your Google Wifi and all devices will keep your router/ISP Box as the default gateway.\nRouting selected devices through VPN # To be able to select the device you want to send through VPN or not (it is working even if you keep the VPN as the default gateway and you want to exclude some devices) you have to install and configure the Policy Base Routing package.\nopkg update opkg install pbr luci-app-pbr Once installed you will have a new Policy Routing menu where you can configure everything needed.\nTo select the gateway for desired local network devices, you can add new policies defining the local IP address or the device hostname with a prerouting rules and the target interface to use as default gateway.\nIn this screenshot the GoogleTV and the MacBookPro are forwarded to the VPN (by default all the devices on my network are not using the VPN).\nSpecific Video Streaming configuration # If like in my example, you are forwarding devices with non-vpn friendly services (Netflix, amazon prime, \u0026hellip;) you can create some custom policies defining the target address and interface.\nFor Netflix, the pbr service is already having a configuration you can simply activate (NB in my case I needed to activate even the AWS one to get it working).\nFor other services you can add a policy like the following one:\nHow to test / debug # For Netflix is easy to know if the traffic is forwarded to the correct interface: the service is (mostly) not working if it is going through the VPN. But, what about other URLs?\nThere are several ways to debug your connection. The simple one if using traceroute and traceroute6.\n$ traceroute www.google.fr traceroute to www.google.fr (216.239.38.120), 64 hops max, 52 byte packets 1 openwrt (192.168.86.1) 4.853 ms 6.957 ms 7.892 ms 2 10.200.0.1 (10.200.0.1) 13.255 ms 12.262 ms 13.713 ms 3 51.255.71.253 (51.255.71.253) 13.344 ms^C The 10.200.0.1 is my tun0 public IP address. This means the traffic is redirected via the VPN.\nIf I\u0026rsquo;m doing the same with Netflix, I excluded from the VPN:\n$ traceroute netflix.com traceroute: Warning: netflix.com has multiple addresses; using 54.155.246.232 traceroute to netflix.com (54.155.246.232), 64 hops max, 52 byte packets 1 openwrt (192.168.86.1) 5.686 ms 5.109 ms 3.737 ms 2 192.168.1.1 (192.168.1.1) 4.605 ms 7.768 ms 5.949 ms 3 80.10.233.201 (80.10.233.201) 8.701 ms 8.956 ms 8.694 ms The traffic is going through my ISP Box having the 192.168.1.1 address.\nI can now surf on the web in a very secure way!! 😎\n","date":"28 novembre 2022","externalUrl":null,"permalink":"/openvpn-on-google-wifi-via-openwrt/","section":"Posts","summary":"","title":"OpenVPN on Google WiFi via OpenWRT","type":"posts"},{"content":"","date":"28 novembre 2022","externalUrl":null,"permalink":"/tags/openwrt/","section":"Tags","summary":"","title":"Openwrt","type":"tags"},{"content":"","date":"30 octobre 2022","externalUrl":null,"permalink":"/tags/dashboard/","section":"Tags","summary":"","title":"Dashboard","type":"tags"},{"content":"When you move everything to \u0026ldquo;smart\u0026rdquo;, you may lose some information. What about if your room temperature is measured and sent to a \u0026ldquo;computer\u0026rdquo; to show it on a dashboard? How you can see the information without a mobile or a PC? It is becoming interesting or required, to have a physical board somewhere.\nI took time to check how to push information over a tablet, but autonomy and especially the \u0026ldquo;hi tech\u0026rdquo; device in the living room, is not fitting into all the family\u0026rsquo;s loved styles. So after a long period, I found a good alternative using an e-ink screen with an ESP32 microcontroller.\nDevice Description # I bought a InkPlate 10\u0026quot; which is giving a correct size board in a complete board. The ESP32 microcontroller is coming with a built-in WiFi controller and is largely used today in many SmartHome devices. This is giving you access to a lot of pre-built resources for any kind of usage.\nIn the board I received, as shown on a board sticker, there is a GPIO controller missing due to the current chip shortage. 😱 Not so critical for me as I didn\u0026rsquo;t plan to use it in my tests.\nTo complete the devices you would love to buy a 3.7V battery pack. This will allow you to use the InkPlate without a permanent power link. You can in this way plan to put it almost everywhere in your house.\nWARNING: take care when you look for the battery about the polarity. On the net (personally, I looked on Amazon) there are a lot of models with reversed polarity.\nDevice Configuration # To start using the InkPlate device you just need to put a firmware in your ESP32 microcontroller and do everything you want: you have a screen, you have a microcontroller\u0026hellip; few lines of code and there you go. In my case, I just wanted to display an HomeAssistant (kiosk) dashboard, in real-time or updated every while during the day. And looking on the NET you will surely find what you need without losing a lot of time to start. For what I needed, the HomePlate repository is having all the information I needed to start with a ready to go firmware.\nYou have a very few steps to do:\ncopy the config_exemple.h file into config.h put the information you need for WiFi, MQTT, \u0026hellip; and the image URL to display. To simplify the board display it must be converted into an image install the PlatformIO cli. On Mac is quite easy with brew brew install platformio Build the homeplate sources with the device connected to the USB-C port to your Mac: pio run Create HA Board Image # As described in the repository, there is a simple way to create a board image. Once again is something you can find online: hass-lovelace-kindle-screensaver.\nThere is a simple docker-compose.yaml file you have to configure with your HA information and start it up. In my case, I preferred to run it on a separed RPi I already had and using for some other tools.\nversion: \u0026#34;3.8\u0026#34; services: app: image: sibbl/hass-lovelace-kindle-screensaver:latest environment: - HA_BASE_URL=http://192.168.xx.xx:8123 - HA_SCREENSHOT_URL=/lovelace-kiosk/0?kiosk - HA_ACCESS_TOKEN=xxxxx - CRON_JOB=0/5 * * * * - RENDERING_TIMEOUT=30000 - RENDERING_DELAY=0 - RENDERING_SCREEN_HEIGHT=825 - RENDERING_SCREEN_WIDTH=1200 - GRAYSCALE_DEPTH=8 - OUTPUT_PATH=/output/cover.png - LANGUAGE=en - ROTATION=0 - SCALING=1 ports: - 5000:5000 volumes: - ./output/:/output Some important things here:\nthe CRON_JOB can be configured to reduce the number of screenshot. RENDERING_SCREEN_HEIGHT and RENDERING_SCREEN_WIDTH with the InkPlate screen size ROTATION depending how you want to display your board (horizontal or vertical) Now that everything is configured you can enjoy your physical dashboard.\n","date":"30 octobre 2022","externalUrl":null,"permalink":"/inkplate-10-home-assistant-board/","section":"Posts","summary":"","title":"InkPlate 10 Home Assistant Board","type":"posts"},{"content":"Washing machines or whatever, there are some devices in our houses that are located far from the living places. So, how to know if it is the time to take of them without checking every 5 minutes? I know, we have a clock and that should be enough, but if you have HomeAssitant you can easily configure an action to notify you when the washing machine cycle is completed.\nTrack the consumption # If your washing machine is not connected by itself, we can track it using the plug with power consumption and on the market today there are a lot of devices proposing this function.\nWiFi # Without any other protocol, you can buy a WiFi power adapter with a power meter. On my side, I\u0026rsquo;ve a couple of Konyks\nThe advanced, more than the protocol, is that it usually this kind of device is quite cheaper. The less interesting part is that you need the WiFi signal in the location you installed your washing machine.\nYou can control them in the Tuya/Konyks application or add them to HomeAssistant, which is allowing to have all the start/stop and meter information.\nZigBee # If you are using this protocol (the one proposed by Philips Hue) and you would like to use its powerful mesh function there are lot of alternatives proposing it with a price remaining quite low. For example the Innr Smart Plug, I\u0026rsquo;m using it too and working very well.\nIt exposes the same information as the WiFi Tuya One.\nI would suggest this kind of device if you already have a lot of other ZigBee devices helping you to extend the signal and reaching easily any remote corner of your home.\nZWave # It is proposing the same benefits as the ZigBee one, but as constructors need a license to produce devices using this protocol, the final price is very high compared to the others. Fibaro is producing very good ZWave devices. I used one of them in the last 4 years and it is excellent in everything. It has much more functionalities and configurations available than the two others we have just seen.\nNOTE/WARNING Just a thing to take in count when you chose your power plug, never mind the protocol you will use, is the max power the plug can provide (in Watt). If you link a high-consumer device which is requiring more than the max available from the plug, the plug itself will shutdown the device considering it has a problem. I had this for several weeks using the Fibaro ZWave plug on the washing machine, before understanding that it was not the good plug for what I wanted 😅\nConfiguring the Notification # Once your washing machine\u0026rsquo;s power consumption is monitored with a power plug you have everything you need to configure Home Assistant.\nFirst of all, we need to create a binary_sensor we will use to detect if the device is working or not. All the plug we have just seen have a ON/OFF information, but this is saying you if the plug is allowing the device to work (giving the power) or not. Is not saying if it is really working.\nIn your configuration.yaml you can create the binary_sensor like the following\nbinary_sensor: - platform: template sensors: washing_machine: value_template: \u0026#34;{{ states(\u0026#39;sensor.machine_a_laver_power_consumption\u0026#39;) | float \u0026gt; 10.0 }}\u0026#34; delay_on: 0:00:30 delay_off: 0:00:30 This is saying to check the sensor.machine_a_laver_power_consumption, when it is over 10W for more than 30 seconds the binary_sensor is considered on. When is moved down to 0 for more than 30 seconds it will be back to off.\nThen, just using the binary_sensor you can create an automation to do \u0026ldquo;what you want\u0026rdquo; when it go on or off. An example to be notified:\n- id: washing_machine_working alias: Machine à laver en fonction trigger: - entity_id: binary_sensor.washing_machine from: \u0026#34;off\u0026#34; platform: state to: \u0026#34;on\u0026#34; action: - service: notify.notify data: message: \u0026#34;Machine à laver en marche {{ states(\u0026#39;sensor.machine_a_laver_power_consumption\u0026#39;) }}.\u0026#34; - id: washing_machine_finished alias: Machine à laver Cycle Terminé trigger: - entity_id: binary_sensor.washing_machine from: \u0026#34;on\u0026#34; platform: state to: \u0026#34;off\u0026#34; action: - service: notify.notify data: message: \u0026#34;Cycle machine à laver terminé.\u0026#34; With this, just keeping your phone you will get notifications when the washing machine is starting and once it finish the cycle.\nThat\u0026rsquo;s all. You can now imagine some cool Automation with your controlled devices\u0026hellip; smart home is very cool and it could help to optimise your consumption!\n","date":"11 octobre 2022","externalUrl":null,"permalink":"/homeassistant-detect-washing-machine-cycle-completion/","section":"Posts","summary":"","title":"HomeAssistant: detect washing machine cycle completion","type":"posts"},{"content":"","date":"11 octobre 2022","externalUrl":null,"permalink":"/tags/power/","section":"Tags","summary":"","title":"Power","type":"tags"},{"content":" OnePlus 2 - Double SIM automatic selection # As I described here there is actually a bug which is preventing a correct usage of what I\u0026rsquo;m going to explain here. But it will work when OnePlus will fix the problem in OxygenOS.\nThe functionalities added to the DualSim part of OxygenOS are really basic: you can select your SIM card for data, your default SIM for Text Message and calls (or let the phone to ask you every time) and that\u0026rsquo;s all. You can\u0026rsquo;t, for example, configure a custom SIM for a contact or group of contacts. The worst thing to me is the useless functions in OnCarSystem, if you let the phone configured to ask you every time you want to make a phone call. In this case, making a call with contact selection on your car system, just open the selection popup on your phone making it useless!\nFor the first problem (the default SIM for contact or group) you can just download another dialer (it works only on phone). For the second one I\u0026rsquo;m just using an automation program which is (was) working really well on the OnePlus 2.\nYou can download two Android application on your phone:\nTasker Dual Sim Control Both two are paid applications, but not too expensive.\nWith tasker you can do what you want with your phone: do an action when a trigger is fired. For example: toggle WiFi when you are connected to the car bluetooth system, disable GPS when you are at home, and many many many others things. Actually is not allowing you to change the dual SIM settings, but for this you can use the Dual Sim Control application which is a standalone application and a tanker plugin.\nSo, the only things you have to do is (for example): Create a trigger which changes the SIM card configuration from \u0026ldquo;Ask every time\u0026rdquo; to \u0026ldquo;SIMx\u0026rdquo; when the phone is connected to the Bluetooth X device; switch back the settings when is disconnected.\nIn this way, when I enter my car, I\u0026rsquo;m selecting the default SIM and I\u0026rsquo;ll be able to make calls without touching the phone.\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/oneplus-2-double-sim-automatic-selection/","section":"Posts","summary":"","title":"OnePlus 2 - Double SIM automatic selection","type":"posts"},{"content":" LG Watch R Review # After two weeks with my new LG Watch R smartwatch I can finally make a little review about it. I preferred to use it a little bit because I wasn\u0026rsquo;t really convincend about the real need of a smartwatch.\n##Installation/First Usage The Android wear watch is really simple to use and pair with your smartphone: naturally you need an Android phone with the Bluetooth 4.0 (or greater) (which means you need at least Android 4.3). If you are not sure about your phone you can visit the followin address, directly from your smartphone, to check the compatibility:\nhttp://www.android.com/wear/check/\nYou should see something like this in case all is ok for you.\nTo pair it with the phone you just then need to install the Wear application (from Google Play Store) and follow the instruction.\n##Applications/Faces/\u0026hellip; The first real problem, surely due to my skepticism, it is to check \u0026ldquo;what can I do with this new watch?\u0026rdquo; A thing to know is that you need to control your WearWatch from your Android phone: an application is installed on your phone and then, a part of this application, is copied to the smartwatch too. This means that all applications already installed on your phone, having a part compatible with a Wear device, will be automatically synchronised directly after the watch pairing (i.e. Runtastic).\nWhat you need then? A cool thing to have is a new personalised \u0026ldquo;Face\u0026rdquo; for your watch. On the Play store you can find many application proposing one (or more) faces. Personally, after some tests, I think the best one is WatchMaker: is an application allowing you to create, or download, as many faces as you want, using also all your watch sensors. A good starting point to download faces is on FacesRepo. Start browse and look for your preferred face (for today\u0026hellip; tomorrow you can change it once again :P).\nI just want to say that a possible battery drain problem could be the face you installed. I tested for example Beautiful Weather Watch Face which completely empty my butter in less than a day. So may attention to what you install :)\n##Battery Life Actually I reached a really good battery life.\nI\u0026rsquo;m not to far from 3 days without any charge!!\nSure, actually I\u0026rsquo;m not looking to the watch any seconds like the first days, but I\u0026rsquo;m receiveing many mails/messages/calls/\u0026hellip; and my watch vibrates and/or shows notifies all day long. Compare to the others SmartWatch on the market (for what I can read on the net) is really good. The battery will then be fully charged in about 1 hour.\n##Included Gadgets The LG Watch R, like any watch you can find on the market today, includes many sensors: 9-Axis (Gyro / Accelerometer / Compass), Heartbeat, Barometre and Microphone. With the accelerometer/gyro, for exemple, the watch screen is automatically switched on when you turn your hand to see the watch! The hearbeat\u0026hellip; ok it works but it is a real useless gadget. If you need a sport watch it\u0026rsquo;s better to buy a sport watch. The Microphone is for\u0026hellip; \u0026ldquo;Ok Google\u0026rdquo;\u0026hellip; and then ask all you want. Whereas almost all the resquests to google require an internet connection\u0026hellip; well\u0026hellip; you need your phone. But anytime I\u0026rsquo;m using it I\u0026rsquo;m really feeling like Micheak Knight :D\n##Conclusion Do I really need a smartwatch? I think not today (and it\u0026rsquo;s too expansive for what you have) Actually an Android Wear smartwatch is just a device you need to use combined with a phone: without it your SmartWatch can\u0026rsquo;t do anything. It allows you to have notifies in real time: the watch vibrates for an incoming call, mail, phone notification, \u0026hellip; And you can read the many information directly on it: who is calling, the mail content (not so easy to read a complex and long mail on the watch), a text message content, \u0026hellip; You can finally leave your phone in your pocket and check if you need to get it out.\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/lg-watch-r-review/","section":"Posts","summary":"","title":"LG Watch R Review","type":"posts"},{"content":" Jawbone UP 24: Review # During my 2 weeks trip to NYC I decided to buy the new Jawbone UP 24 (I already tested in the past the first version of this band) to monitor my activities during the vacation. The main difference with the first version is the constant connection, via bluetooth, to the telephone (no more connection with audio jack needed!); this means you can constantly check your progresses without removing your band.\nIf you don\u0026rsquo;t know it, Jawbone UP / UP 24 is an activity monitor system. You can monitor your day movements (number of steps) or your night sleep. You simply have to wear the band, choose your modality (day/night) and that\u0026rsquo;s all.\n###Installation The first thing you need to do before the UP usage, is the download of the app (iOS/Android applications available), connect the band with the application, create an UP account and choose your personal information (weight, height, \u0026hellip;). These information are then uploaded to the UP band (for the 24 version, via bluetooth, otherwise you need to connect the band to the audio jack of your smartphone).\nInto the settings page you can then select all other settings for your band:\ninactivity alarm morning wake-up calls activities notifies All these settings must be uploaded to the band after changes.\n###Usage The use of the band, after the initial settings, is really simple: you have nothing to do, just wear it and change modality when needed. To change modality (sleep/day) you can use the button on the band that means you don\u0026rsquo;t need an external device to use it\u0026hellip; BUT, if you need to check any information, a device (Android/iOS) is required. On the band you just have a led showing the set mode.\nAll information are then automatically upload to your phone, and in the UP application you can check your progress\nThe day progress show your number of steps with a graph indicating when, during your activity, you were more active:\nThe night activity show information about: how long did it take you to fall asleep, deep sleep time, light sleep, if you woke up during the night, \u0026hellip;\nThat is, in my opition, the interesting part of this band: the sleep monitoring allow you to check way are you tired in the morning, even if you think you have to sleep a lot :) If you set an wakeup alarm, the band check the best time to wake you up checking your sleep activity and the time of your alarm. In the alarm settings you can set the wakeup time and and delta time. For example: wakeup 7AM o\u0026rsquo;clock, delta 20 minutes, means Jawbone check your activity starting at 6:40 and ending at 7:20. If you are in a light sleeping and you are already in movement, UP vibrate to wake you up. If you are in a deep sleep period UP let you sleep until 20 minute past your alarm. Great, isn\u0026rsquo;t it? :D\n###Connection You can connect your Jawbone UP account to other services and share your progresses.\nIs just a way to centralise your activities and calories burned or acquired.\n###Conclusion It\u0026rsquo;s an interesting device that helps you tracking your everyday activity and help you to stay in motion (any allarm is a vibration). If during your everyday working activity your are sitting at a desk, like me, UP remeber you that you when it\u0026rsquo;s time to get a pause and move a little bit (I set 45 minutes of inactivity).\nIt\u0026rsquo;s a good wakeup alarm: anytime it vibrates in the morning I open my eyes and wakeup without problems (almost any day\u0026hellip; sometimes children wake me up before :P)\nI\u0026rsquo;m not sure about the band longevity (my wife used the UP 3 weeks). I think if you really want to track your activities and stay in motion, UP could be your best friend; if you just want a gadget maybe you will use it some days and then\u0026hellip; in a tray\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/jawbone-up-24-review/","section":"Posts","summary":"","title":"Jawbone UP 24: Review","type":"posts"},{"content":" Nexus 7: restore to factory default # Even if I never \u0026ldquo;hacked\u0026rdquo; my Nexus device after some month (I suppose due to application installation), my device crashes and, after reboot, i was stuck on Google boot image. I try to leave it an hour booting with no success. So\u0026hellip; factory reset. Yes, but\u0026hellip; how I can run a factory reset for a crashed device? Reading online there are some forum posts with combination that I honestly didn\u0026rsquo;t understand.\nHere the combination I tested many time to wipe your device:\nReboot your device pressing Power Button + Sound Down Button, at the same time until you can see the fast boot menu ![](/images/nexus-7-restore-to-factory-default/00-and.png) Using volume buttons move to Recovery Mode menu and then start it using the power button Your Nexus 7 will reboot and, after a while, you should see the android robot with a red exclamation mark and the information no data on the screen. Here press Power Button + Sound Up Button at the same time for about 2 seconds and then release the two buttons at the same time. Recovery menu should appear Here you can choose, moving into menu with the sound buttons, the Wipe Data menu (and if you want Wipe Cache) At the end you have a Reboot entry into menu to restart your device. Hope this help ","date":"8 octobre 2022","externalUrl":null,"permalink":"/nexus-7-restore-to-factory-default/","section":"Posts","summary":"","title":"Nexus 7: restore to factory default","type":"posts"},{"content":" Galaxy Note 2: scrittura a mano libera # È qualche giorno ormai che ho fatto il passaggio da iPhone al galaxy Note 2. Non ho ancora avuto modo di testare a fondo tutte le funzionalità, ma in questa prima settimana ci sono diverse cose che mi hanno stupito positivamente. Fra queste il riconoscimento della scrjttura con SPen! Ricordo ancora con il mio primo palmare con Windows mobile, la fatica che feci per imparare come dovevo scrivere le lettere per fare in modo che venissero riconosciute dal dispositivo. Beh\u0026hellip; qui non è più necessario: riesce a riconoscere anche la mia scrittura in corsivo (e bisogna dire che in quanto a grafia, non è che sia molto lontano dalla scrittura di un medico :)).\nFinalmente il piacere della scrittura a mano libera digitale :)\nStay tuned.\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/galaxy-note-2-scrittura-a-mano-libera/","section":"Posts","summary":"","title":"Galaxy Note 2: scrittura a mano libera","type":"posts"},{"content":" Connectors: Apple vs Sumsung # Or, in other words, \u0026ldquo;when someone really love Apple and became blind\u0026rdquo;! Reading this article I noticed that sometimes people talk without connecting the brain. Apple produce only 2 proprietary phone connectors in the latest 10 years, and Samsung hundreds.\nYes, that\u0026rsquo;s true, but: how many phones produced Samsung in 10 years, and how many Apple?\nYou can check easily looking these pages:\nSamsun: http://www.gsmarena.com/samsung-phones-9.php Apple: Do you really need a page to count the Apple iPhones?\nSo: Samsung 18 adapters and 809 phones: 2,22% Apple 2 adapters 7 phones: 28,5%\nYes, Apple can change adapter but it\u0026rsquo;s not necessary to talk about it as the event of the year and attack anyone saying that Apple changes to a proprietary connector and not to a standard. That\u0026rsquo;s true!! Other companies are converging to the same connector (the micro usb) Apple prefers the proprietary one. You know Apple (yes you know!!) in Europe you should use the standard!\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/connectors-apple-vs-sumsung/","section":"Posts","summary":"","title":"Connectors: Apple vs Sumsung","type":"posts"},{"content":" Steve Jobs e la sua fortuna (una parte\u0026hellip;) # Oggi navigando in rete fra i vari giornali, mi sono imbattuto in questo articolo su Steve Jobs. Ok, effettivamente in questi gironi non è difficile trovare articoli su di lui, ma non essendo un maniaco di morti famose (la parola fun in questo caso renderebbe poco) non sono stato a cercare qualsiasi cosa su Jobs, per elogiarlo o criticarlo post mortem.\nAd ogni modo\u0026hellip; malgrado conoscessi già la storia di Pixar, sono rimasto stupito dalle cifre e dai passaggi che mi mancavano. Per riassumere, soprattutto per i non francofoni, Steve Jobs ha comprato nel 1986 la Lucasfilm Computer Graphics per 10 milioni di dollari (chi non ha 10 milioni di dollari per comparsi una societá?) ed ha creato da essa la Pixar. Con un accordo con la Disney per la diffusione dei loro prodotti, hanno fatto il primo grande successo con Toy Story ed hanno poi continuato con altri titoli del calibro di Nemo e Monster\u0026amp;Co.. Avevano avuto così tanto successo che alla fine la Disney ha deciso che non avrebbe più diffuso film della Pixar, diventata ormai concorrente. Quindi, avendo tagliato le gambe alla societá di Jobs che poteva produrre ma non aveva i mezzi per diffondere i film, se l\u0026rsquo;è poi comprata per soli 7,4 miliardi di dollari ed un 6% di azioni Disney cedute allo stesso Steve! Anno 2006, cioè in 20 anni ha \u0026ldquo;guadagnato\u0026rdquo; 7,39 miliardi di dollari più un 6% in azioni (e stiamo parlando di quella societá di nome Walt Disney, quindi non sono sicuramente bruscolini!). E nel frattempo non dobbiamo dimenticare che ha fatto risorgere anche Apple che era al limite della bancarotta, non saprei quantificare la cosa ma credo che siano numeri molto più alti di quelli di Pixar. Inutile dire che qualsiasi cosa sia stata toccata da quest\u0026rsquo;uomo si è trasformata in oro.\nHo scritto questo per rispondere in modo \u0026ldquo;civile\u0026rdquo; a tutti quelli che lo hanno parecchio criticato (non ultimo Stallman) in questi giorni. Alla fine Steve Jobs non era solo Apple, e anche in quel caso, sebbene le politiche aziendali fossero (e lo sono tutt\u0026rsquo;ora) molto particolari, come dargli torto? Per fare soldi bisogna cercare di fidelizzare il cliente e trovarne sempre di nuovi e Apple ha sempre fatto questo: una volta che ti sei comprato un prodotto della mela morsicata sei \u0026ldquo;obbligato\u0026rdquo; a prendere tutto da loro. I prodotti, grazie alla marketing \u0026ldquo;alla rovescia\u0026rdquo;, diventano subito delle icone (faccio trapelare solo alcune informazioni pilotate sulle novità in uscita e lascio che siano i forum in internet a fare il resto): \u0026ldquo;se non hai una iPhone, beh, non hai un iPhone\u0026rdquo;\nQuindi credo che gli si possa criticare tutto, ma credo che lo si debba ringraziare per il fatto che \u0026quot; la tecnologia\u0026quot; sia al livello a cui la conosciamo oggi. Non ha inventato niente (e da qui le virgolette su tecnologia) ma ha sempre avuto una visione futuristica sulle invenzioni di altri. Quindi anche se avevi le tue idee e (a detta di altri) eri uno stronzo, grazie per le tue idee!\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/steve-jobs-e-la-sua-fortuna-una-parte/","section":"Posts","summary":"","title":"Steve Jobs e la sua fortuna (una parte...)","type":"posts"},{"content":" Paese che vai usanza che trovi: libretto di circolazione # Dopo aver visto le differenze fra la ASL italiana e la sécurité sociale francese, vediamo le peripezie per avere la carte grise per la macchina.\nPremessa: siamo arrivati in Francia a fine gennaio 2011 con due macchine, una Peugeot 206 ed una Peugeot 5008, le cui assicurazioni scadevano rispettivamente ad inizio marzo e ad inizio giugno. Un dettaglio importante é che la 206 era una macchina acquistata in Francia, immatricolata in Francia ed usata per 5 anni in Francia, prima di essere immatricolata in Italia.\nA febbraio cominciamo a verificare le démarche à faire per poter riportare la 206 al suo vecchio paese d\u0026rsquo;origine e alla prefettura ci danno la lista dei documenti da presentare. Ovviamente, quando ci presentiamo agli uffici della prefettura con il nostro bel gruzzoletto di documenti, scopriamo che ne manca uno. Su questo gli uffici pubblici italiani e quelli francesi sono identici (purtroppo)! Quindi, vista l\u0026rsquo;inutilità dell\u0026rsquo;insulto verso il funzionario pubblico (anche se é divertente faro in italiano se la persona di fronte non può capirti :)) cerchiamo di recuperare anche questo fantomatico documento prima della scadenza dell\u0026rsquo;assicurazione. Il documento mancante e che in Francia si richiede per poter immatricolare un veicolo acquistato all\u0026rsquo;estero é il certificat de conformité. In pratica, la casa produttrice del veicolo, deve assicurare che, anche se acquistato all\u0026rsquo;estero, é conforme alle normative europee (e in particolare francesi) e che quindi può essere immatricolato in Francia. Ora, facciamo finta che non siamo arrivati in Francia da un altro paese della UE, facciamo anche finta che guarda caso le macchine sono prodotte da una società francese, ma\u0026hellip; la 206 era già immatricolata in Francia prima del breve passaggio Italiano. Quindi, chiamata a Peugeot che dice che basta inviare la carta di circolazione italiana e un assegno di soli 150 euro e ti fanno avere in massimo una settimana il certificato di conformità a casa. 150 euro per dirmi che una macchina che é già stata immatricolata in Francia può essere immatricolata nuovamente?? Va beh\u0026hellip; Ad ogni modo la celerità di Peugeot é incredibile: lettera imbucata nella tarda mattinata del giorno X risposta ricevuta la mattina del giorno X+1. Con questa riusciamo a fare la nuova carta di circolazione, l\u0026rsquo;assicurazione e le targhe.\nA maggio abbiamo dovuto naturalmente fare gli stessi passi per poter immatricolare la 5008 (conoscendo fortunatamente tutto quello che era necessario presentare alla prefettura) e anche in questo caso la regola dell\u0026rsquo;X e X+1 di Peugeot é stata rispettata. Ho come il sospetto che al tizio di Peuogeot al PC inserisca il numero del telaio e stampi i dati risultanti (anche perché entrambe le macchine sono state prodotte nelle fabbriche Peugeot francesi), quindi 150 euro per una fotocopia a colori credo che sia il massimo che abbia mai pagato.\nA freddo, dopo qualche mese, ci siamo detti che fortunatamente avevamo solo macchine francesi, non oso immaginare la richiesta di un certificato di conformità a Toyota in Giappone o, peggio ancora, a FIAT in Italia! Considerate le poste italiane, le prime 3 lettere sarebbero andate perse, la quarta mi avrebbero detto che volevano anche una foto della macchina, la quinta che FIAT non era autorizzata a fare quel tipo di documento e la sesta, dopo aver tirato giù qualche madonna al telefono con un tizio della fabbrica torinese che si giustifica dicendo che non avevano capito che volevamo proprio quel tipo di documento, arriva finalmente a destinazione\u0026hellip; nel frattempo é passato 1 mese e hai dovuto ricomprare un\u0026rsquo;altra macchina perché ti serviva per andare a lavorare.\nQuindi, mi raccomando, se entrate in Francia e dovete immatricolare un veicolo che avevate comprato all\u0026rsquo;estero: certificat de conformité!\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/paese-che-vai-usanza-che-trovi-libretto-di-circolazione/","section":"Posts","summary":"","title":"Paese che vai usanza che trovi: libretto di circolazione","type":"posts"},{"content":" Quando é bizzarra internet? # Giusto una di quelle cose che vedi e tici \u0026ldquo;ma come é possibile che facciano dei giri cosi per raggiungere il PC che ho qui di fianco?\u0026rdquo;\nPer i non addetti ai lavori Stavo cercando di capire perché avessi problemi a raggiungere il PC di casa (che sta a 10km massimo da dove sono io ora). E nella lista dei nodi che passo per raggiungere il PC c\u0026rsquo;é un \u0026ldquo;London\u0026rdquo; ?!? Come dire che l\u0026rsquo;algoritmo del commesso viaggiatore applicato al network non funziona molto bene! ;)\nSperiamo che i GPS non diventino cosi nel calcolare le rotte!\ntraceroute jenkins.home traceroute to jenkins.home (86.198.208.123), 30 hops max, 60 byte packets 1 WRT54GL (192.168.23.1) 1.425 ms 3.124 ms 3.785 ms 2 reverse.completel.net (92.103.32.129) 4.936 ms * 5.518 ms 3 * * * 4 * * * 5 reverse.completel.net (213.244.0.234) 12.481 ms 12.706 ms 13.794 ms 6 reverse.completel.net (213.244.0.242) 14.228 ms 6.411 ms 6.855 ms 7 prs-b6-link.telia.net (213.248.93.41) 7.556 ms 8.472 ms 9.322 ms 8 prs-bb2-link.telia.net (80.91.246.56) 9.602 ms 10.427 ms prs-bb1-link.telia.net (80.91.246.54) 11.963 ms 9 prs-b7-link.telia.net (80.91.252.146) 12.514 ms 13.125 ms 8.086 ms 10 tengige1-8-0-5.pastr1.Paris.opentransit.net (193.251.251.105) 8.415 ms tengige1-13-0-7.pastr1.Paris.opentransit.net (193.251.250.221) 8.483 ms tengige1-13-0-5.pastr1.Paris.opentransit.net (193.251.254.153) 8.450 ms 11 pos0-1-4-0.lontr1.London.opentransit.net (193.251.242.18) 20.516 ms 20.060 ms 18.805 ms 12 * * * 13 * * * 14 * * * 15 * * * 16 * * * 17 * * * 18 * * * 19 * * * 20 * * * 21 * * * 22 * * * 23 * * * 24 * * * 25 * * * 26 * * * 27 * * * 28 * * * 29 * * * 30 * * * ","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/quando-e-bizzarra-internet/","section":"Posts","summary":"","title":"Quando é bizzarra internet?","type":"posts"},{"content":" JailBreak - La vera verità # La verità è in realtà la mia verità. Ho preso spunto da un post, con i milioni di commenti a cui ho preso parte, che è uscito qualche giorno fa su iPhone Italia: l'esperienza di un utente del sito che illustrava i pro e i contro (secondo lui) dell'avere o meno il JB su iPhone.\nPrima di iniziare vorrei premettere che sul mio telefono, così come su tutti quelli venduti in italia anche dagli operatori telefoni stessi, non c'è alcun blocco della BaseBand (l'apparato telefonico del telefono), lasciando quindi la possibilità di usarlo con qualsiasi SIM. Ovviamente il JB diventa \"necessario\" se ho, per esempio, un 2G e lo voglio usare qui in italia.\nDetto ciò partiamo con la narrazione...\nDue giorni fa ho deciso che volevo, per la terza volta, JailBreakkare il mio iPhone (la verità è che volevo provare un navigatore senza però spendere 60€ per poi scoprire che era una schifezza), per la terza volta perchè avevo già fatto due JB in passato che avevo tolto dopo un paio di giorni e ogni volta per provare qualche applicazione particolare.\nQuindi mi sono scaricato il rinomato tool di JB, ho attaccato il cellulare al PC e ho premuto sull'unico bottone dell'applicazione; questo per dire che è impossibile sbagliare qualcosa. 1 minuto di orologio dopo il mio telefono era \"evaso di prigione\". Mi installo il navigatore, ne verifico il funzionamento e lo attacco a caricare, e basta! Non ho messo nessun altro programma che poteva occuparmi memoria e processore!\nPerchè dico questo? Perchè l'indomani, quando con il mio telefono comincio il mio quotidiano viaggio vero l'ufficio, attacco il telefono all'accendi sigari, imposto la destinazione... e via!\nDopo un po' mi accorgo che, malgrado l'iPhone sia in carica (con il caricabatteria rapido da 1000mA!!) la batteria si scarica lentamente durante l'utilizzo! Stupito della cosa spengo il navigatore... ma durante la giornata mi accorgo comunq\nue che c'è qualcosa che non va. Il telefono infatti perde, in stadby, il 10% di carica ogni 50/60 minuti!!!\nOk che il 3GS non è noto per la durata della batteria, ma non era mai stato così una zozzeria! Tornando a casa raggiungo l'apice dell'assurdo: 1 ora di viaggio navigando in rete con il 3G -\u0026gt; 70% della batteria consumata! Che a conti fatti significa che io non potrei usare il telefono per più di 2 ore e che non raggiunge le 10 ore se lo lascio solo in stadby!\nCosa inconcepibile ma vorrei risottolineare quanto avevo detto sopra: il JB è solo un tasto e non ho installato niente. Però può essere ovviamente successo qualcosa di anomalo, o durante il JB, o durante l'installazione del navigatore crakkato!\nLa sera quindi perdo dell'altro tempo per ripristinare il telefono da zero e, per non rischiare che mi vengano ributtati su file \"con problemi\" decido di non recuperare il vecchio backup del telefono (la conseguenza è perdere le impostazioni delle applicazioni installate e del telefono, perchè tutto il resto è sincronizzato con Outlook, quindi un po' di roba ma ci si può passare sopra).\nFinita la procedura e raggiunto il 100% della carica, stacco il telefono e ci navigo qualche minuto e poi lo lascio in stadby (durata batteria 99% ore 00:30). Mi alzo la mattina alle 7.30, controllo lo stato della batteria 92%!! Sembra a posto...\nNon sto a fare la telecronaca minuto per minuto di quello che è successo al mio telefono, ma lascio la schermata di utilizzo che parla da sola!\n![](/images/jailbreak-la-vera-verita/00-IMG_0001.png)\nDopo 20 ore di StandBy, di cui 3 di utilizzo (e le ora di uso sono in navigazione su 3G o wireless)... senza mai caricarlo mi trovo con il 15% di carica. Quindi mi faccio tranquillamente una giornata!\nL'unica precisazione che voglio fare al riguardo è che le impostazioni del telefono sono quelle base: quindi luminosità di Default (che credo sia al 50%) con regolazione automatica, 3G e WiFi sempre attive, nessuna notifica Push e controllo manuale delle caselle E-Mail.\nA questo punto direi di chiudere con il JB. Non credo che la colpa sia davvero il JB, anche perchè le altre 2 volte che lo avevo effettuato non avevo notato un degrado di prestazioni così evidente. Il problema è che se capita una cosa analoga, come risolverlo? Io con il TaskManager non ho visto nessun task \"sospetto\", spegnimento e accensione del telefono (anche con HardReset) non risolvevano il problema... quindi? Ripristinare nuovamente il software!\nHo un sospetto sul problema: credo che si fosse \"bloccato\" in qualche modo il GPS perchè, per la prima volta, ho visto che la posizione riportata su GoogleMap era completamente sballata (20Km da dove mi trovavo) e si è ripresa solo dopo qualche minuto. Lo uso spesso durante i miei viaggi in treno per vedere dove mi trovo ed era davvero la prima volta che mi succedeva. Quindi il problema immagino fosse lì ma non avevo idea di come poterlo risolvere.\nAmmesso che il JB vada a buon fine, non è detto che la vostra vita sia sempre rose e fiori. A me è capitato che andando ad installare un'applicazione presente sul repository Cydia (dove veniva indicato che era per la mia versione del firware), il telefono si bloccasse completamente! Riavvio post installazione e bloccato sulla mail. L'unica cosa fattibile, che mi hanno suggerito in realtà, era collegarsi al telefono via SSH e cancellare la cartella dell'ultima applicazione installata e riavviare.\nOvviamente è necessario aver preventivamente installato OpenSSH sull'iPhone, avere la Wireless del telefono attiva e conoscerne l'indirizzo IP. Essendo il telefono bloccato se non sono valide tutte e tre le cose, la sola cosa fattibile è: RIPRISTINARE!!!\nPotranno esserci molti punti a favore del JailBreak, come per esempio l'uso del Bluetooth per lo scambio di file (che sul 3GS non va e a me non serve), il multitasking che potrebbe tornare utile in alcuni casi, ma si può fare tranquillamente a meno di questa cosa... cos'altro? Forse molte cose, ma personalmente non voglio rischiare di dover ripristinare il telefono ogni 2 giorni, o dever litigare con l'ssh per accederci e cercare di farlo ripartire. Faccio già troppe di queste cose con il PC al lavoro... e il telefono l'ho comprato per telefonare (navigare, leggere la posta, ... ma in sostanza per poterlo usare). Quindi l'iPhone 3GS ha già tutto quello che puoi volere da un telefono, con i suoi pregi e difetti come ogni altro, senza andare a sbloccarlo.\nVolete un telefono da \"maltrattare\" e per farci tutte le prove che volete? Andate a rispolvere l'OpenMoko... anche se probabilmente molti di quelli che \"insultano\" i possessori di iPhone che non fanno il JB e si vantano di essere degli smanettoni nemmeno conoscono! :)\nQuesta è la mia opinione ovviamente, maturata a seguito di una serie di esperienze. Non ho giudicato senza provare, ma i problemi in cui mi sono imbattuto, che mi hanno obbligato a ripristinare per ben due volte in due giorni, non mi fanno vedere di buon occhio il JB.\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/jailbreak-la-vera-verita/","section":"Posts","summary":"","title":"JailBreak - La vera verità","type":"posts"},{"content":" Flash e iPhone # Da quando ho iPhone, per quanto mi sia stupito delle potenzialità di safari mobile, mi sono imbattuto spesso in siti web interamente o parzialmente (es il menù di navigazione) realizzati in flash. È risaputo che nei browser mobile flash non è ancora interamente supportato; nel caso di Apple è completamente non supportato.\nVisto che in giro per la rete ho trovato spesso notizie contrastanti che lo davano o quasi pronto o di impossibile realizzazione, mi sono messo ad indagare meglio.\nA quanto pare, al di la di eventuali lavori in merito da parte di Adobe, sembra che sia impossibile \u0026ldquo;accettarlo\u0026rdquo; a causa delle attuali politiche di Apple: non è permessa l\u0026rsquo;esecuzione di programmi e script in background!\nCredo e spero che la politica al riguardo verrà cambiata, quantomeno su alcune cose potrebbero essere fatte delle eccezioni. Se è vero che flash diventa un processo separato rispetto a quello del browser, è anche vero che per l\u0026rsquo;utente finale è percepito come unico applicativo!\nSperiamo che lo zio Jobs, per una volta, faccia le cose come si deve!\n- Posted using BlogPress from my iPhone\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/flash-e-iphone/","section":"Posts","summary":"","title":"Flash e iPhone","type":"posts"},{"content":" Blog da iPhone # Tempo fa leggevo un post su un blog di non ricordo chi, dove diceva che era stata riscoperta la voglia di \u0026ldquo;bloggare\u0026rdquo; grazie ad iPhone.\nEffettivamente, ora che per curiosità sto provando la stessa cosa, devo ammettere che le comodità non sono poche.\nCerto\u0026hellip; scrivere con una real qwerty è indubbiamente più comodo, però, almeno nel mio caso, manca sempre il tempo per farlo. Anche volendo sfruttare il viaggio in treno che mi porta da e verso Milano, è una rottura dover prendere il portatile per postare \u0026ldquo;nonsobenecosa\u0026rdquo; sul blog.\nPoterlo fare \u0026ldquo;in movimento\u0026rdquo;, come direbbe una nota pubblicità, non ha prezzo! In qualsiasi situazione ti trovi, purché abbia con te il fido telefono, puoi scrivere e postare\u0026hellip; agganciandoci magari qualche foto e video.\nConcordo quindi con il ritorno dell\u0026rsquo;amore per il blog! Provare per credere.\n- Posted using BlogPress from my iPhone\n","date":"8 octobre 2022","externalUrl":null,"permalink":"/it/blog-da-iphone/","section":"Posts","summary":"","title":"Blog da iPhone","type":"posts"},{"content":"","date":"24 août 2022","externalUrl":null,"permalink":"/tags/shelly/","section":"Tags","summary":"","title":"Shelly","type":"tags"},{"content":"Going ahead making my home smarter and, hopefully, reduce the electricity invoice by being more environmentally friendly\u0026hellip; if adding electric devices can be considered in this way 😩\nThis time I want to share how I made my water heater smarter\u0026hellip; and I decided to do it by looking at my electricity consumption graphic during my 2 weeks holidays.\nDuring the night the consumption was high (at least higher than during the day) and for the rest of the day, only the smart devices, internet box, camera, \u0026hellip; Why that? Because our water heaters (we have 2) are statically configured to work every night at about 2 o\u0026rsquo;clock. But nobody will use the water\u0026hellip; and it was heat every day. 😱😱 That made me crazy\u0026hellip; I could shut them down before leaving (but I dismembered)\u0026hellip; but in this way, the first day at home you won\u0026rsquo;t have hot water (because it takes several hours).\nWater Heater power circuit? # In my case, I had a \u0026ldquo;Day Night circuit breaker\u0026rdquo; piloted by an electronic clock.\nThere is a 25A circuit used to power up the water heater and a second one, 2A, powering the clock and giving the signal to the day/night circuit breaker. When the clock gives a signal to the day/night device, this will let the power from the 20A circuit go to the water heater. In some cases, you can replace the clock with the electrical company day/night switch but the working process is exactly the same.\nWhich module? # It depends on your installation, but the thing to keep in mind is that a water heater is a power consumption device, and It is maybe not a good idea to power it up via a smart device. Following the previous schema, the easiest way to make it smarter is to replace the clock with a smart switch.\nAfter a while of looking online I decided to test a Shelly device: the Shelly Plus 1. I only needed a little change in my electrical configuration\nNow the signal to activate the day/night circuit breaker is given by the shelly device. Quite easy isn\u0026rsquo;t it? 😎\nConfiguration # First of all, you need to configure the device within the Shelly application. Just follow the instruction provided with the device and in a couple of minutes, you are ready to go.\nIf you don\u0026rsquo;t have a SmartHome Box or you don\u0026rsquo;t want to integrate it, you can do everything within the Shelly application: To proceed to Home Assistant you may need to install the latest firmware version for your Shelly device (always inside the application) which is required by the HA integration.\nHome Assistant # To import your Shelly device into Home Assistant you need to install the Shelly Integration and then provide your device IP.\nThere are some configurations to let the full control of the Shelly device within Home Assistant (Update, Reboot, \u0026hellip;) but to work with your water heater you just need the switch one.\nFrom now on, it is like any other automation. How do you want to control it? When? Based on a sensor or not? \u0026hellip; And just configure accordingly.\nIn my case, the French electric company gave me 2 timeframes each day with a reduced cost: one during the night and one during the day. I decided to let it work during the night timeframe.\n- id: water_heater_on alias: \u0026#34;Water Heater ON\u0026#34; mode: parallel trigger: - platform: time at: \u0026#34;01:24:00\u0026#34; condition: - condition: state entity_id: input_boolean.vacation state: \u0026#34;off\u0026#34; action: - service: switch.turn_on entity_id: switch.shellyplus1_XXXXXXXX_switch_0 - id: water_heater_off alias: \u0026#34;Water Heater OFF\u0026#34; mode: parallel trigger: - platform: time at: \u0026#34;07:24:00\u0026#34; condition: - condition: state entity_id: input_boolean.vacation state: \u0026#34;off\u0026#34; action: - service: switch.turn_off entity_id: switch.shellyplus1_XXXXXXXX_switch_0 So, coming back to my initial frustration, there is an added flag compared to the static configuration I had before:\ncondition: - condition: state entity_id: input_boolean.vacation state: \u0026#34;off\u0026#34; The Water Heater is started at the defined time, but only if I\u0026rsquo;m not on holiday (and not home). Hopefully, I will be able to be more green 😎\n","date":"24 août 2022","externalUrl":null,"permalink":"/smart-water-heater-with-home-assistant-and-shelly-device/","section":"Posts","summary":"","title":"Smart Water Heater with Home Assistant and Shelly device","type":"posts"},{"content":"","date":"23 août 2022","externalUrl":null,"permalink":"/tags/fibaro/","section":"Tags","summary":"","title":"Fibaro","type":"tags"},{"content":"Making your home smart allows several benefits, but the most important is that you can automate everything you want based on special events: close the cover if the sun is hotting the house; turn off all the lights when you are leaving, \u0026hellip; If well parametrized it is making all your home, much more environment friendly.\nToday I would like to talk about the \u0026ldquo;Mechanical Ventilation System\u0026rdquo; (VMC here in France): a system used to renovate internal air by reducing bad smell and humidity.\nNormally, the simplest device is having 2 speeds (slow and fast), is running 24H/24, and we can manually change the speed by acting on a switch. It is exactly at this point that automation is becoming interesting. For example: if the humidity is too high in one of the rooms in which VMC is extracting the air, we can auto-move to the fastest speed. But, how to proceed?\nVMC wiring diagram # In the double-speed VMC, there are two capacitors used to \u0026ldquo;help the engine\u0026rdquo; start and run at the correct speed; the power is going to the engine via these two capacitors. The speed is selected with the proper wire going through a single capacitor or both two.\nHow to connect a Smart Module? # We need to choose a double Switch Smart module (allow to select the slow or fast mode) and able to send \u0026ldquo;indefinitely\u0026rdquo; the power to the engine. Saying these because online we can find a ton of tutorials talking about the \u0026ldquo;Fibaro Roller Shutter\u0026rdquo;, the module used to automate the covers. With the old version (FGR-222) the device settings allow to remove the \u0026ldquo;stop timer\u0026rdquo; (engine always on). With the new device (FGR-223) Fibaro removes this setting and the engine will be stopped after a while. It is now a dedicated Cover device.\nTo automate the VMC (or anything else requiring permanent power from the module), on the Fibaro side we need to look to all the FGS-xxx devices. The latest double switch is now the FGS-224.\nBut Fibaro means Z-Wave protocol, which means SmartBox Hub understanding the protocol. If you are starting or you don\u0026rsquo;t want a Hub (or you want a cheaper device 🤑), you can check the new Shelly modules which are working on your home WiFi simplifying a lot of your Smart Home configuration. For the VMC automation, you can check the Shelly 2.5. In my case, I already had a SmartHome Box and, even due to the location I wasn\u0026rsquo;t sure about the WiFi signal, I preferred the Fibaro automation using the FGS-224.\nThe wiring is really simple and you can follow what is proposed directly on the device documentation.\nWe can modify a little bit the first schema and introduce our Smart Module:\nNB: If you are choosing a different one, you have to check the documentation to be sure how to link it up.\nThe module is providing the power to the VMC via the Q1 or Q2 link that you need to connect to the slow and fast speed (never mind which one on what, you can name them later into your smarthome box).\nHome Assistant Integration and Configuration # First of all, you have to pair the device based on the decisions you made before: Wifi, Z-Wave, SmartBox Hub, or not, \u0026hellip;\nOnce the Fibaro is paired with your box you have a new device with the following properties:\nYou don\u0026rsquo;t need any specific setting over the device; the important part here is the 2 switches (in my screenshot \u0026ldquo;VMC\u0026rdquo; and \u0026ldquo;(2)\u0026rdquo;\u0026hellip; no idea why by default it is named in this shitty way 😅). Each one is acting over a Qx out pin, and if you linked in the correct way, it will send the power to the proper VMC speed cable.\n⚠️ WARNING you don\u0026rsquo;t have to power on the two switches at the same time. Your VMC could not like the way power is provided.\nFor this reason, in my installation, I based the switch changes on an input_select component with 3 states Off, \u0026ldquo;Speed 1\u0026rdquo; and \u0026ldquo;Speed 2\u0026rdquo;.\ninput_select: vmc: name: VMC State icon: mdi:fan options: - \u0026#34;Off\u0026#34; - \u0026#34;Speed 1\u0026#34; - \u0026#34;Speed 2\u0026#34; Then 3 automations are reacting to the input_select state change.\nautomation: - id: vmc_slow alias: VMC Speed 1 trigger: - platform: state entity_id: input_select.vmc to: \u0026#34;Speed 1\u0026#34; action: - service: switch.turn_off entity_id: switch.vmc_2 - delay: 5 - service: switch.turn_on entity_id: switch.vmc - id: vmc_fast alias: VMC Speed 2 trigger: - platform: state entity_id: input_select.vmc to: \u0026#34;Speed 2\u0026#34; action: - service: switch.turn_off entity_id: switch.vmc - delay: 5 - service: switch.turn_on entity_id: switch.vmc_2 - id: vmc_off alias: VMC Off trigger: - platform: state entity_id: input_select.vmc to: \u0026#34;Off\u0026#34; action: - service: switch.turn_off entity_id: switch.vmc_2 - service: switch.turn_off entity_id: switch.vmc The actions are changing the switch states to be sure we never have the 2 switches on at the same time\n- service: switch.turn_off entity_id: switch.vmc_2 - delay: 5 - service: switch.turn_on entity_id: switch.vmc Before it will switch off the one we don\u0026rsquo;t need anymore, wait 5 seconds (or whatever\u0026hellip; I even think it is not necessary at all, but I preferred to keep it safe with a little delay) and switch on the other switch.\nWith a sensor we can track the VMC status and speed:\n- platform: template sensors: vmc_status: friendly_name: VMC Status icon_template: \u0026gt; {% if is_state(\u0026#34;switch.vmc\u0026#34;, \u0026#34;on\u0026#34;) and is_state(\u0026#39;switch.vmc_2\u0026#39;, \u0026#39;off\u0026#39;) %} mdi:fan-speed-1 {% elif is_state(\u0026#34;switch.vmc_2\u0026#34;, \u0026#34;on\u0026#34;) and is_state(\u0026#39;switch.vmc\u0026#39;, \u0026#39;off\u0026#39;) %} mdi:fan-speed-2 {% elif is_state(\u0026#39;switch.vmc\u0026#39;, \u0026#39;off\u0026#39;) and is_state(\u0026#39;switch.vmc_2\u0026#39;, \u0026#39;off\u0026#39;)%} mdi:fan-off {% else %} mdi:fan-alert {% endif %} value_template: \u0026gt; {% if is_state(\u0026#39;switch.vmc\u0026#39;, \u0026#39;on\u0026#39;) and is_state(\u0026#39;switch.vmc_2\u0026#39;, \u0026#39;off\u0026#39;) %} Vitesse 1 {% elif is_state(\u0026#39;switch.vmc_2\u0026#39;, \u0026#39;on\u0026#39;) and is_state(\u0026#39;switch.vmc\u0026#39;, \u0026#39;off\u0026#39;) %} Vitesse 2 {% elif is_state(\u0026#39;switch.vmc\u0026#39;, \u0026#39;off\u0026#39;) and is_state(\u0026#39;switch.vmc_2\u0026#39;, \u0026#39;off\u0026#39;)%} Off {% else %} failed {% endif %} For a simple integration, this is everything you need. You can display everything in a Lovelace card to control your VMC:\ntype: entities entities: - entity: sensor.vmc_status icon: \u0026#39;\u0026#39; secondary_info: last-changed - entity: input_select.vmc state_color: true title: VMC show_header_toggle: false giving you something like:\nFull Automation # This is quite cool, isn\u0026rsquo;t it? But making the VMC smart is to give full autonomy to your home, and keep always the good parameters (the VMC speed). If we keep only a button to change it manually, there is not many differences from the initial classical button.\nIn my case I installed a device in each room, giving me the temperature and the humidity. Depending on the version and the brand of the device you can have even the pressure, the CO2, \u0026hellip; or several other parameters.\nSo, we can easily automate based on the humidity of rooms!!\n- id: shutdown_vmc_based_humidity alias: Auto VMC Off Humidity \u0026lt; 55 trigger: - platform: time_pattern minutes: \u0026#34;/30\u0026#34; condition: - and: - condition: numeric_state entity_id: sensor.humidity_114 below: 55 - condition: numeric_state entity_id: sensor.humidity_112 below: 55 - condition: numeric_state entity_id: sensor.humidity_93 below: 55 - condition: template value_template: \u0026#34;{{ not is_state(\u0026#39;input_select.vmc\u0026#39;, \u0026#39;Off\u0026#39;) }}\u0026#34; action: - service: input_select.select_option target: entity_id: input_select.vmc data: option: \u0026#34;Off\u0026#34; - id: startup_vmc_based_humidity alias: Auto VMC On 55 \u0026lt; Humidity \u0026lt; 70 trigger: - platform: time_pattern minutes: \u0026#34;/30\u0026#34; condition: - and: - condition: numeric_state entity_id: sensor.humidity_114 above: 55 - condition: numeric_state entity_id: sensor.humidity_114 below: 70 - condition: numeric_state entity_id: sensor.humidity_112 above: 55 - condition: numeric_state entity_id: sensor.humidity_112 below: 70 - condition: numeric_state entity_id: sensor.humidity_93 above: 55 - condition: numeric_state entity_id: sensor.humidity_93 below: 70 - condition: template value_template: \u0026#34;{{ not is_state(\u0026#39;input_select.vmc\u0026#39;, \u0026#39;Vitesse 1\u0026#39;) }}\u0026#34; action: - service: input_select.select_option target: entity_id: input_select.vmc data: option: \u0026#34;Vitesse 1\u0026#34; - id: increase_vmc_speed_based_humidity alias: Auto VMC Speed 2 Humidity \u0026gt; 70 trigger: - platform: time_pattern minutes: \u0026#34;/30\u0026#34; condition: - and: - or: - condition: numeric_state entity_id: sensor.humidity_114 above: 70 - condition: numeric_state entity_id: sensor.humidity_112 above: 70 - condition: numeric_state entity_id: sensor.humidity_93 above: 70 - condition: template value_template: \u0026#34;{{ not is_state(\u0026#39;input_select.vmc\u0026#39;, \u0026#39;Vitesse 2\u0026#39;) }}\u0026#34; action: - service: input_select.select_option target: entity_id: input_select.vmc data: option: \u0026#34;Vitesse 2\u0026#34; In my configuration, I\u0026rsquo;m checking 3 rooms: the kitchen, the bathroom, and the toilet (each one is having the VMC air link). We need to take care of the \u0026ldquo;OR\u0026rdquo; and \u0026ldquo;AND\u0026rdquo; to prevent 2 actions to be triggered at the same time.\nIf the humidity in all the rooms is less than 55%: power off the VMC If the humidity is in a selected interval for all the rooms: Speed 1 If the humidity is higher than a defined value (70%) in at least one room: Speed 2 About the humidity values you can put whatever you want\u0026hellip; on my side, I based the automation on the idea that the internal humidity, to be comfortable, should be between 40% and 70%.\nI\u0026rsquo;m triggering with a time pattern and not directly on the humidity value to prevent a lot of speed changes when the humidity is around the selected limit. With the time trigger, once changed, the configuration will be kept for at least 30 minutes.\nWhen automation has a valid trigger and condition, the action is changing the input_select, and not directly the switch. In this way, we are keeping our configuration safe because we have only one component (the input_select) controlling our VMC and the two switches. Even if 2 automation, due to a wrong configuration, will be fired at the same time, they are changing the input_select value that can be single, resulting in a specific final VMC state.\nWhen everything will be OK, your VMC will react to the humidity to keep a confortable home environment\n","date":"23 août 2022","externalUrl":null,"permalink":"/smart-vmc-mechanical-ventilation-system/","section":"Posts","summary":"","title":"Smart VMC (Mechanical Ventilation System)","type":"posts"},{"content":"","date":"23 août 2022","externalUrl":null,"permalink":"/tags/vmc/","section":"Tags","summary":"","title":"Vmc","type":"tags"},{"content":"","date":"4 janvier 2022","externalUrl":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java","type":"tags"},{"content":"","date":"4 janvier 2022","externalUrl":null,"permalink":"/tags/programming/","section":"Tags","summary":"","title":"Programming","type":"tags"},{"content":"When we are talking about code quality we always land to the code coverage check: we need to be sure to test all the code lines based on the provided inputs.\nWhat is the best code coverage? # We are mostly talking about 80% of code coverage. This seems a reasonable value to have something good without losing a lot of time\u0026hellip; but is that enough? How we can say we have \u0026ldquo;enough coverage\u0026rdquo; to be sure we can put in production, without fear, just after each change?\nA good and simple rule can be:\nif we change something in the code: an if a loop, a value, variable init, \u0026hellip; a test should fail somewhere.\nThis is because changing something in the code should modify the way your application is working: same input different output. What about if the output is not changing? That\u0026rsquo;s a good question, but in a basic way we could say: never mind that code, we are still having the expected result.\nLet now check the following function\n@GetMapping public ResponseEntity\u0026lt;List\u0026lt;Book\u0026gt;\u0026gt; getAllBooks(@RequestParam(required = false) String title) { try { List\u0026lt;Book\u0026gt; books = Optional.ofNullable(title) .filter(t -\u0026gt; !t.isEmpty()) .map(bookRepository::findByTitleContaining) .orElseGet(bookRepository::findAll); if (books.isEmpty()) { return new ResponseEntity\u0026lt;\u0026gt;(HttpStatus.NO_CONTENT); } return new ResponseEntity\u0026lt;\u0026gt;(books, HttpStatus.OK); } catch (Exception e) { return new ResponseEntity\u0026lt;\u0026gt;(null, HttpStatus.INTERNAL_SERVER_ERROR); } } It will generate a JSON response containing the list of books retrieved from the DataBase. If we provide the title parameter it will retrieve books with the provided word(s) within the title, if the parameter is empty or null it will retrieve all the database books. Then it will create a response based on the retrieved value.\nWhat are the tests we should create to validate this simple code? I can try to list them here:\nwith null title, check if thefindAll method is invoked with an empty title, check if thefindAll method is invoked with a valid title, check if findByTitleContaining with the title parameter if the List\u0026lt;Book\u0026gt; books is empty, the answer should be HttpStatus.NO_CONTENT if the List\u0026lt;Book\u0026gt; books is not empty, the answer should be HttpStatus.OK with the list of retrieved books if an error is created somewhere, the answer must be HttpStatus.INTERNAL_SERVER_ERROR Then to go further, what about the values provided to title? Can it be any possible char, number, different alphabet, \u0026hellip; ? How many tests do we have to write to be sure we have a good code coverage?\nProperty Testing # The side problem with what we have just seen is the maintainability of your code. Imagine we wrote only 6 tests (but with 6, we are using a single title possible value!!) if we change anything in the method we maybe have to change the 6 tests at once. This means each change will require a 6 times greater effort than without any test.\nBut we have a proper coverage and in all other cases, I can be sure any other change around can\u0026rsquo;t break this code.\nA simple solution can be to use PBT, Property-Based Test. We will write a single test that triggers hundred/thousand tests at once with the same code. The following example is using the jqwik library:\n@Property public void testReadAllBooksEmpty(@WithNull @ForAll String title) { ResponseEntity\u0026lt;List\u0026lt;Book\u0026gt;\u0026gt; response = cut.getAllBooks(title); if (title != null \u0026amp;\u0026amp; !title.isEmpty()) { verify(bookRepository).findByTitleContaining(title); verify(bookRepository, never()).findAll(); } else { verify(bookRepository).findAll(); verify(bookRepository, never()).findByTitleContaining(title); } assertEquals(\u0026#34;Unexpected HTTP Status Code\u0026#34;, response.getStatusCode(), HttpStatus.NO_CONTENT); } NOTE: as we have an if in the test, I know it should be 2 different tests instead. I just wanted to keep it \u0026ldquo;extreme\u0026rdquo; to show how simple can be. I didn\u0026rsquo;t want to be a Unit Test Purist 😅\nThe @Property annotation is specifying that the method is a PBT. Then the @ForAll annotation over a parameter is a way to say we want to inject different values for all the tests and the @WithNull is testing with a null title; there are several other parameters and different ways to control how you want to manage the values injected into the title parameter.\nWith this basic configuration, it is running by default 1000 tests with 1000 different values (with the empty one too):\ntimestamp = 2021-12-31T17:40:22.380933, BookControllerTest:testReadAllBooks = |-------------------jqwik------------------- tries = 1000 | # of calls to property checks = 1000 | # of not rejected calls generation = RANDOMIZED | parameters are randomly generated after-failure = PREVIOUS_SEED | use the previous seed when-fixed-seed = ALLOW | fixing the random seed is allowed edge-cases#mode = MIXIN | edge cases are mixed in edge-cases#total = 3 | # of all combined edge cases edge-cases#tried = 3 | # of edge cases tried in current run seed = -8434577657060517927 | random seed to reproduce generated values and we are validating that we are calling the correct bookRepository method based on the title parameter and the response is empty with NO_CONTENT status code. We know it is always an empty response because the bookRepository is mocked and we didn\u0026rsquo;t initialize it.\nGetting the first list we wrote, with this single test method we tested:\nwith null title check if thefindAll method is invoked with an empty title check if thefindAll method is invoked with a valid title check if findByTitleContaining with the title parameter if the List\u0026lt;Book\u0026gt; books is empty the answer should be HttpStatus.NO_CONTENT We can then create a second one, for example, to test the information are correctly returned when the repository is giving valid book objects.\nCode changes # As we said at the beginning, good code coverage does not allow to change stuff without failing tests. For example:\nList\u0026lt;Book\u0026gt; books = Optional.ofNullable(title) //.filter(t -\u0026gt; !t.isEmpty()) .map(bookRepository::findByTitleContaining) .orElseGet(bookRepository::findAll); removing the empty filter, if we have a good test, should fail.\ntimestamp = 2021-12-31T17:57:31.304953, BookControllerTest:testReadAllBooksEmpty = org.mockito.exceptions.verification.WantedButNotInvoked: Wanted but not invoked: bookRepository.findAll(); -\u0026gt; at net.mornati.springnativepoc.controller.BookControllerTest.testReadAllBooksEmpty(BookControllerTest.java:36) However, there was exactly 1 interaction with this mock: bookRepository.findByTitleContaining(\u0026#34;\u0026#34;); -\u0026gt; at java.base/java.util.Optional.map(Optional.java:260) 🤩😎\nBut using PBT we have also some added tests that we didn\u0026rsquo;t plan. Imagine for example we want (or an error in the code, so we don\u0026rsquo;t want) to filter title longer than 10 chars. Code can be something like\nList\u0026lt;Book\u0026gt; books = Optional.ofNullable(title) .filter(t -\u0026gt; !t.isEmpty()) .filter(t -\u0026gt; t.length() \u0026lt;= 10) .map(bookRepository::findByTitleContaining) .orElseGet(bookRepository::findAll); As in the 1000 automatic tests we have a lot of different titles with different sizes, when we run the test without changing anything else, the code is not working as expected\norg.mockito.exceptions.verification.WantedButNotInvoked: Wanted but not invoked: bookRepository.findByTitleContaining( \u0026#34;\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0026#34; ); -\u0026gt; at net.mornati.springnativepoc.controller.BookControllerTest.testReadAllBooksEmpty(BookControllerTest.java:33) However, there was exactly 1 interaction with this mock: bookRepository.findAll(); -\u0026gt; at java.base/java.util.Optional.orElseGet(Optional.java:364) we are calling the findAll method instead of the findByTitleContaining.\nWith manual tests, this case can be also covered by chance: we have to use a title longer than 10 chars within our test. So automatically we have better coverage without changing everything and, as I said at the beginning better maintainability.\nConclusion # I tried to show you how powerful can be a PBT and why we should use them. It is for sure a very simple example. In real-life PBT are much more complex: several parameters, custom object parameters, \u0026hellip; the JQwik framework I used is allowing all of these possibilities. How to know if it should be good to write a PBT instead of a simple Unit Test? Basically, we can say, any time you are calling a method with \u0026ldquo;static\u0026rdquo; parameters, you should write property or parametrized test instead.\nvar result = myMethod(\u0026#34;xxx\u0026#34;); var result2 = myMethod2(2, new Car(\u0026#34;Peugeot\u0026#34;)); In my opinion, these are tests you should try to rewrite to have a better and automatic code coverage.\n","date":"4 janvier 2022","externalUrl":null,"permalink":"/why-we-should-use-property-testing/","section":"Posts","summary":"","title":"Why we should use Property testing?","type":"posts"},{"content":"","date":"30 décembre 2021","externalUrl":null,"permalink":"/tags/docker-images/","section":"Tags","summary":"","title":"Docker-Images","type":"tags"},{"content":"Yes, it seems that even in 2021 not all IT people understood the power behind containers. Docker is one of the implementations but for sure the most known and used today on the developer laptops.\nIt is going better than several years ago, but even this year both for developer and production usage, I took part in discussions on the net that made me think that not all the IT guys understood why it became the base of everything today.\nI could get back some examples used in my previous blog.\nDeveloper\u0026rsquo;s laptop benefits # A long time ago, in a galaxy far away (ok it was only in Italy, but is still far away), I used to spend the first X days configuring my laptop all the time I was changing projects. Because the application server was not always the same, the same for the database, ant or maven, the JDK 1.3 or 1.4 (yeah, I already said you I\u0026rsquo;m old? 😅). So the first step was: I\u0026rsquo;m a sysadmin and I need to understand how to install all the requirements before even starting to code anything.\nah you are on Linux? Our documentation is only for Windows, why are you on Linux?\nAnd sometimes the project was even not working the first day because I wrongly installed some part of the whole setup. The DEV-LONGSTART.md was always too long and the first week was usually lost in shitty stuff.\nDocker makes an abstraction layer to all these things. Today I don\u0026rsquo;t even know how I should install most of the tools I\u0026rsquo;m using: PostgreSQL, Redis cluster, RabbitMQ, \u0026hellip; I remove the SysAdmin part and I finally have a DEV-QUICKSTART.md file which is explaining you have to install the docker-engine, maven/gradle, and which version of JDK you should use (thanks to sdkman this part can always being ignored). Then you run a docker-compose and you are ready to local test everything you developed. The database contains some base data, the cache is configured, etc. You move from X days to X hours (or even less).\nIs not the only benefit but is, in my opinion, the most interesting one.\nProduction benefits # It was working on my laptop\nHow many times have we used, as developers, this sentence? And it was always the truth, but why? Because the production environment was neven like the developer laptop: Windows vs Linux, Sun JDK vs IBM/Oracle JDK, File System case insensitive vs case sensitive, \u0026hellip; \u0026ldquo;I saw things you wouldn\u0026rsquo;t believe\u0026rdquo;.\nDocker, or it\u0026rsquo;s better to talk about containerd today because many production systems are not using the docker-engine anymore, is helping to fix all these problems. The test made by a developer on its laptop, with Docker if you remember the previous point, is testing with the same \u0026ldquo;binary\u0026rdquo; that will be then used to run the application in production. What is running and tested on the CI is the same thing deployed in production. And if it was running somewhere else, the only thing that could be wrong is the \u0026ldquo;production system configuration\u0026rdquo;\u0026hellip; it should be easy to debug.\nBut we should get out from mind model with always used. The Docker container is NOT a virtual machine. You have not compared it to VMWare, you should never do \u0026ldquo;things inside a container, how I will persist them?\u0026rdquo;. The immutability of Docker images is helping us because if it runs at least once if I restart the container (or better recreate it from the base image) it should work again\u0026hellip; and if it is not the case it is surely due to the environment and not the image. So, even if we can change the Docker container content, install new packages, etc. is not in this way that we should work! We should change our mind model and directly create our image \u0026ldquo;ready to go\u0026rdquo;. If I need new packages I create a new image for my application.\nI don\u0026rsquo;t want to start technically describing how docker is working and why I\u0026rsquo;m saying all these things. There are a ton of articles (even on my blog), documentations, books, \u0026hellip; It is important to start using something knowing how it is working, reading things, understanding, \u0026hellip; and then check if this new technology is fixing any of your problems, if it is not maybe you don\u0026rsquo;t need to use it. Moving only from tech A to tech B because \u0026ldquo;anybody is using it now\u0026rdquo; or \u0026ldquo;I want to be skilled on it to be market aligned\u0026rdquo;, but changing nothing else will not really help you: you won\u0026rsquo;t be skilled and you fix any problems (you are maybe adding some others instead).\n","date":"30 décembre 2021","externalUrl":null,"permalink":"/what-docker-is-for/","section":"Posts","summary":"","title":"What Docker is for?","type":"posts"},{"content":"","date":"30 décembre 2021","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","title":"Devops","type":"tags"},{"content":"I\u0026rsquo;m an old geek and I used to say this to my team all the time; not because I love being old but because I think this is helping me today trying to understand why we are proposing some new methodologies or technologies. History is helping us to prevent past problems, experience in the IT world the same.\nToday we are talking about SRE (Site Reliable Engineer) in many companies, and it is happening several years after the USA for the same practice. But we are not always applying it with a base knowledge (what problem we are solving with this?) and creating job titles because the \u0026ldquo;new generations\u0026rdquo; want this.\nI will try to give here examples I had in my career to give a \u0026ldquo;why we are introducing SRE\u0026rdquo; (or DevOps before). If you are just looking for a complete guide to SRE I suggest you read one of the following ebooks https://sre.google/books/. I read this one\nand it is a very interesting lecture.\nBut, let\u0026rsquo;s get back to my history.\nIn the beginning we had Dev and SysAdmin # When I started my IT career, the real one with a salary I mean, it was 17 years ago 😱😱. I was a simple developer. My job was to write a piece of code, let it working and that\u0026rsquo;s all: someone else was in charge of building the final binary and some other teammates to put and run it in production.\nThe Silos IT: you have the best experts in each domain each one in charge about only a part of the IT environment, the dev, the CI engineer, the SysAdmin, what can be wrong? Problems here are coming in between the different roles. The developer team was configuring the dev laptop with a JVM which was not the one used for the build and in production (because it was a proprietary one, thanks to IBM 😠), the application server was not the same one, for the same reason and because the laptop resources were far from the production ones.\nSo sometimes the build was failing or not producing the same result (how many of you worked before the Java annotations were invented? What about XDoclet ?) and once in production, the software may have problems that the SysAdmin team was not able to fix. Because was not the Application server itself but maybe the application, maybe the build. How to find it out?\nThe classical example here, I had several times: after some days/weeks the application was failing/restarting and usually, it was related to the memory limit. So what the SysAdmin team could do?\nFirst step: add RAM. But this is just reducing the number of problems. Once a X applications still fail and trigger the duty. Second step: create a cronjob to auto restart the application and auto-fix the problem. 🤩😎 I know somebody of you is smiling because today we would not think of this solution anymore (I hope so 😅), but how the SysAdmin team can fix something that was not known by them? The application they were running was developed by the dev team (and built by the CI one). So? Monitoring. SysAdmin team installs tools to get data to try to understand and then forces the dev team to read the data and give them the solution. 🤯\nDevOps practice # As things were not working well, we introduced something different which was trying to remove/reduce this no-mans-land between each role.\nDevOps is a set of practices that combines software development (Dev) and IT operations (Ops). It aims to shorten the systems development life cycle and provide continuous delivery with high software quality.[1] DevOps is complementary with Agile software development; several DevOps aspects came from the Agile methodology.\nSource\nWe can define it differently: give more \u0026ldquo;power\u0026rdquo; to the developer team making the CI and System near the code, and to the ones creating it. But, in my opinion, things did not work as expected: DevOps move from practice to Job title; it was then about automating system installation (Ansible, Puppet, \u0026hellip;) and we tried to find a large different definition to something that wanted to be only a set of practices to fix the problem I described at the beginning.\nThen it was the time about Docker: cool we can run on the dev laptop the same thing we will run in production and in the same way. No more it was running on my laptop. But moving to Docker without a real problem to fix, produced completely crazy things.\nHow can I save what changed within my Docker VM (😩)?\nIt is not like VMWare.\nBut, as Docker container was failing sometimes, and it was not starting back automatically, Kubernetes came to fix it. The orchestrator to manage them all. We moved from a simple system and simple problem to something more complex without really knowing why. (because for me K8s is fixing another problem than the one I was describing)\nSRE to fix everything # Because DevOps failed, we introduce then something coming from US and Google, and if it is coming from Google it is surely working better 😅\nSite reliability engineering (SRE) is a set of principles and practices[1] that incorporates aspects of software engineering and applies them to infrastructure and operations problems.[2] The main goals are to create scalable and highly reliable software systems.[2] Site reliability engineering is closely related to DevOps, a set of practices that combine software development and IT operations, and SRE has also been described as a specific implementation of DevOps.[2][3]\nSource\nI guess reading the description we can all say: it is almost the same thing DevOps wanted to do.\nWhat is (a little bit) changing is how we look at our production application: the important thing is to have an application providing a service for our customers and respecting the defined SLAs; automate everything to be fast in case of problems; observe everything to be able to take actions.\nAn example: a cart API that should answer in less than 20ms and with an uptime of 99,9%. These are our SLAs. The next step is to follow the production values; an easy game with just NGINX access logs, a big query and grafana. How many calls are not 2xx and how many are outside the 20ms required?\nFYI Google is providing a cool set of tools for this: https://github.com/google/slo-generator. We are using it on all our applications and the results are really crazy.\nAny action taken around the application environment is to respect your SLA and/or improve your contract. To understand that is everything: imagine you are deploying a new PullRequest to the production environment. Which is then moving 20% of HTTP calls to 400 or 500 (never mind, but it is not 2xx). The time this new version remains online is reducing your uptime (impacting your Error Budget). How long it will take to fix it?\nAre you able to make a rollback? Are you able to fix the code and put a new version online? So with this fail, we can take actions around:\nThe code quality, PR review process, non-regression testing, \u0026hellip;. The CI efficiency: how long is the build? What are the steps auto played? Is there any manual step? The CD efficiency: how the application is deployed? Is there any manual step? Is there monitoring checking for problems and auto rollback? The production observability DevOps or SRE target is about the efficiency of our IT. Time to market is important, automation is contributing to it but to be able to automate everything we need to observe our environments.\nDev and SysAdmin are still existing but the job changed over these years. And changed because of the problems I tried to describe to you. There is a part of the IT which is in charge of only one of the two groups, but each other should have at least a limited vision about what is happening in the other part of the world. Working in completely isolated Silos can work, but most of the time we are not fixing the root cause of the problems because nobody is taking care of what is happening in the middle.\n","date":"30 décembre 2021","externalUrl":null,"permalink":"/sre-or-why-it-practices-changed/","section":"Posts","summary":"","title":"SRE or why IT practices changed?","type":"posts"},{"content":"What is this? Where did this passion come from and when I started? Sometimes I\u0026rsquo;m thinking about everything I did in my career and what each step contributed to what and where I\u0026rsquo;m today.\nEarly Years # In the first years of my career, when I was a \u0026ldquo;simple\u0026rdquo; developer coding and learning all day long, I started going out of my comfort zone. To code with your team is \u0026ldquo;easy\u0026rdquo; (yeah, I know not always, but it is my story and I use the words I want 😂), but what about teaching others? What about going on short missions to help to fix bugs? This is what I did and, trust me, it was not so easy for the younger me.\nMy company at that time ( Bytecode if someone is interested in it) was RedHat partner and thanks to this share, I started teaching \u0026ldquo;everything around Java\u0026rdquo;; mostly it was the JBoss training, but RedHat has some other training in the Java panorama. And with the training weeks, I even started as a consultant. We can resume the job I did as \u0026ldquo;come and help us doing what we are unable to understand\u0026rdquo;. At that time I thought both two activities were very stressful: you can\u0026rsquo;t be prepared for the unknown, but today I know it was the best thing I could do to be stronger and to quickly enlarge my knowledge.\nThe Debugger # The missions were always about 1 week long with the same pattern:\nthe first day morning: technical meeting to take information about the architecture, about the code and explaining the problem the first day afternoon: prepare the laptop with everything needed to help to debug the problem. Debug, Test, Check, Ask, \u0026hellip; it was my week. The last day: report about the solution, when found, or about a possible way to fix the bug by the company itself. For sure each time, a \u0026ldquo;3/4 years experience guy\u0026rdquo; coming into a company with \u0026ldquo;much more veteran people\u0026rdquo; was frowned upon. And this was my feeling too. What do I have to teach these guys? This is the project they are working on every day, in a framework they master and they have more experience than I\u0026rsquo;ve today. Each time\u0026hellip; everywhere in Europe. What helped me was to think: \u0026ldquo;they are skilled for sure, but if they called someone outside it is because they found nothing at all\u0026rdquo;. So here we go\u0026hellip;\nIn few hours I needed to understand how they code the project, how they configure the servers (Clouds were only in the sky at that time 😂), how I can access everything and starting making debug, load tests, code review, \u0026hellip; A \u0026ldquo;3/4 years experience guy\u0026rdquo;! I don\u0026rsquo;t remember if each time I had a success, but I\u0026rsquo;m sure most of the time I helped the company fixing the problem or finding a possible solution. Each time, anyway, they were happy I could help them. Each time I was happy too and surprised to see how I could go far in debugging things and finding the root cause. Each time it was a real big pleasure. As a developer, it is the same when you find the code to fix the thing you looked at all day long, but 10 times more. Because I was a developer needing to understand even much more than just a piece of code: I needed to understand networks, servers and JVMs configurations, Databases, \u0026hellip; and everything related to applications developed and deployed by someone else.\nToday # Now my job is a bit different, but when the situation arises I\u0026rsquo;m still always helping to debug things. Not with the same stress of the beginning, but always with the same pleasure and always with a big smile on my mouth in the end.\nI\u0026rsquo;m managing technical people, the new generation of developers: the \u0026ldquo;Cloud Developers\u0026rdquo;\u0026hellip; and I\u0026rsquo;m not always able to find a way to make them love this part of the job. The code is cool for sure, the code is what we can see and what our \u0026ldquo;customers\u0026rdquo; are using\u0026hellip; but if we are not able to make it work smoothly in production, or even just deploy it in production, all we did became completely useless.\nSo, \u0026ldquo;younger developer\u0026rdquo;, trust me: the production is not only a matter of System Administrators, especially today with everything managed by someone outside the company. In the same way, you are debugging your code, checking \u0026ldquo;step by step\u0026rdquo; each variable value, you can debug your infrastructure. Find at which step a call can block; understand from which components an HTTP call is coming to your webserver; understand how the application server is managing it; the memory and CPU interaction; the database calls, \u0026hellip; there is a ton of things to know 😱😅🤩. Don\u0026rsquo;t look just at your IDE\u0026hellip; the world can be even better outside.\nAnd when you will then come back to your IDE you will know. You will know how your code will work in production. You will know how to write better and performant code.\nTrust me: the whole IT is love and pleasure 😎\n","date":"2 octobre 2021","externalUrl":null,"permalink":"/professional-debugger/","section":"Posts","summary":"","title":"Professional Debugger","type":"posts"},{"content":"Here we show a way to build RPMs with Jenkins using a Makefile. Now we will show a Jenkins based build (without create Makefle).\nSo, you can directly create a new Job in your Jenkins using the Free Style creation method and adding a shell build step. Inside the text area you can put something like this:\ntestrel=$(/usr/bin/git diff HEAD~1 | awk '/[\\t ]*\\+[\\t ]*Release/ { print \"NEWREL\"; exit 0 }') if [ \"$testrel\" != \"NEWREL\" ]; then echo \"There is no new release in the rpm spec files - do not rebuild.\" exit 0 fi rm -rf rpmbuild ${JOB_NAME}.tar.gz mkdir -p rpmbuild/{BUILD,RPMS,SOURCES/${JOB_NAME},SPECS,SRPMS} tar --exclude-vcs --exclude='rpmbuild' -cp * | (cd rpmbuild/SOURCES/${JOB_NAME} ; tar xp) cd ${WORKSPACE}/rpmbuild/SOURCES tar -cvzf ${JOB_NAME}.tar.gz ${JOB_NAME} cd ${WORKSPACE} cp misc/specs/*.spec rpmbuild/SPECS/ sed -i \"s/^[\\t ]*Source0:.*/Source0: ${JOB_NAME}.tar.gz/g\" rpmbuild/SPECS/*.spec sed -i \"s/^[\\t ]*%setup[\\t ]\\+-n[\\t ]\\+.*/%setup -n ${JOB_NAME}/g\" rpmbuild/SPECS/*.spec rpmbuild --define \"_topdir %(pwd)/rpmbuild\" -ba rpmbuild/SPECS/*.spec The first line of the script checks for git log to find if Release is changed inside the spec file (that should be naturally committed as resources of your project); the project will be built only if you modified the Release inside the spec!\nAfter that the operation is like the one proposed in the Makefile: creation of tar.gz source archive, creation of rpm-build directories, build rpm.\nSo, you can choose to put your build code completely inside Jenkins or create a Makefile and link your build process with your project (changes in project that requires build process changes won\u0026rsquo;t impact Jenkins configuration.\n","date":"13 septembre 2021","externalUrl":null,"permalink":"/build-rpms-for-a-git-github-project-with-jenkins/","section":"Posts","summary":"","title":"Build RPMs for a Git (Github) project with Jenkins","type":"posts"},{"content":"A continuous integration system, like Jenkins, that is not created for project \u0026ldquo;outside java world\u0026rdquo;, can be used today for many build activities. You can, for example, find plugin for iOS project CI build, android platform, python project, \u0026hellip;\nHere I\u0026rsquo;ll shown a way to use it to build RPMs in CI.\nFirst think, for simplicity in Jenkins build script, I suggest you to use a Makefile for your project. Following an example created for the OpenSymbolic and Kermit projects.\nTOPDIR = $(shell pwd) DATE=\"date +%Y%m%d\" PROGRAMNAME=kermit-webui RELEASE=0.0.3 TMPDIR=/tmp BUILDDIR=build all: rpms manpage: messages: bumprelease:\t#setversion: build: clean echo $(TOPDIR) echo \"- Create Changelog file\" git shortlog \u0026gt; changelog.txt echo \"- Create new $(TMPDIR)/$(BUILDDIR)\" mkdir -p $(TMPDIR)/$(BUILDDIR) mkdir -p $(TMPDIR)/$(BUILDDIR)/$(PROGRAMNAME) echo \"- Copy existing Kermit sources\" rsync -raC --exclude .git . $(TMPDIR)/$(BUILDDIR)/$(PROGRAMNAME) echo \"- Remove useless files\" rm -Rf $(TMPDIR)/$(BUILDDIR)/$(PROGRAMNAME)/src/sqlite.db #\techo \"- Rename $(PROGRAMNAME) in $(PROGRAMNAME)-$(RELEASE)\" #\tmv $(TMPDIR)/$(BUILDDIR)/$(PROGRAMNAME) $(TMPDIR)/$(BUILDDIR)/$(PROGRAMNAME)-$(RELEASE) echo \"- Compressing $(PROGRAMNAME) directory\" tar -czf $(PROGRAMNAME)-$(RELEASE).tar.gz -C $(TMPDIR)/$(BUILDDIR) $(PROGRAMNAME)/ echo \"- Moving source package in dist dir\" mkdir -p ./dist mv $(PROGRAMNAME)-$(RELEASE).tar.gz ./dist clean: -rm -rf dist/ -rm -rf rpm-build/ -rm -rf $(TMPDIR)/$(BUILDDIR) clean_hard: clean_harder: clean_hardest: clean_rpms install: build manpage install_hard: clean_hard install install_harder: clean_harder install install_hardest: clean_harder clean_rpms rpms install_rpm restart install_rpm: restart: recombuild: install_harder restart clean_rpms: -rpm -e kermit-webui sdist: messages new-rpms: bumprelease rpms pychecker: pyflakes: money: clean async: install /sbin/service httpd restart testit: clean unittest: rpms: build manpage sdist mkdir -p rpm-build cp dist/*.gz rpm-build/ rpmbuild --define \"_topdir %(pwd)/rpm-build\" --define \"_builddir %{_topdir}\" --define \"_rpmdir %{_topdir}\" --define \"_srcrpmdir %{_topdir}\" --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' --define \"_specdir %{_topdir}\" --define \"_sourcedir %{_topdir}\" --define \"vendor Think\" -ba misc/specs/kermit-webui.spec The important part is the one inside build where we create the source .tgz file (with the correct name) to use later, in the rpms part of the makefile, to create the RPM. There are different way to create it and, maybe, this one is not the best you can create; later we will a see a different way to configure it without using the makefile and downloading sources from Github.\nAfter the Makefile creation you can try to compile your project simply running make command in the folder where you have created the Makefile (usually the project root folder).\nNow we can configure a new project inside Jenkins, that should be a free style project with a build step with execute shell configuration. You can just put make and Jenkins will build the project. Here we have an example that will get created RPM and will update a local yum repository that will be uploaded on a server using ftp at the end of build step.\nNow you have a Jenkins that will build a new RPM after any commit (or once a day, depending on your build configuration) and upload the new RPM on an online repository. Easy and working :)\n","date":"13 septembre 2021","externalUrl":null,"permalink":"/build-rpms-using-jenkinshudson/","section":"Posts","summary":"","title":"Build RPMs using Jenkins/Hudson","type":"posts"},{"content":"On August 31st, Docker surprises the world with a news about the docker-desktop application: it won\u0026rsquo;t be free anymore. Even if it is a normal and legit decision, this can be a hard decision for big companies as the final invoice can have consequences on the IT budget.\nThe same day I discovered, thanks to AkihiroSuda Medium Post, that a possible alternative for MacOSX exists\u0026hellip; but I needed to make some enhancements to what was described in the post as my specific use cases required more things.\nInstallation # The lima installation, thanks to homebrew, is really simple and everything is managed:\nbrew install lima Once finished there are 2 main commands available on your PC: lima to access to the virtual machine and execute \u0026ldquo;linux\u0026rdquo; commands; limactrl to control the machine, create, start, stop, \u0026hellip;\nAs Lima is automatically forwards all the VM ports to the host and shares the volumes, everything is as easy as with the docker-desktop. The main difference is that by default it is not using the docker-engine but the containerd directly instead. But for a standard / simple usage this is enough.\nRun and Use containers # The CLI to use to interact with the default containerd is nerdctl\u0026hellip; but in the end the big difference is only in the script name, because all the commands (even the compose one) are there.\nTo simplify the usage, just add an alian on your Mac to directly execute the right command:\nalias docker=\u0026#34;lima nerdctl\u0026#34; Docker API # Containerd is not exposing an API like the docker one, this means for some application it is impossible to interact and control docker. For example, in Java unit test with Testcontainers this interaction is mandatory. But Lima is Linux and Docker is OSS\u0026hellip; so you can configure it to use docker instead of containerd. As AkihiroSuda suggested in its post comments, this is quite simple:\ncurl -fsSL https://get.docker.com | lima lima dockerd-rootless-setuptool.sh install Then you have to access to the docker.sock from your Mac\u0026hellip; this can be done with the following command:\nssh -p 60022 -i ~/.lima/_config/user -o NoHostAuthenticationForLocalhost=yes -L ~/docker.sock:/run/user/$(id -u)/docker.sock 127.0.0.1 Once done you will have a docker.sock file in your home folder. Just configure the application requiring the Docker API, to use the socked within your home folder. To test if everything is working well, from your Mac you can run the following command that is equivalent to run the docker images command.\ncurl --unix-socket ~/docker.sock http://localhost/images/json It is working but is quite annoying in the end, anytime you need to interact with the Docker API you have to remember to run this command.\nWARNING: before to run the ssh command, check if the docker.sock file already exists on your Mac. If it was not deleted by a previous execution, ssh couldn\u0026rsquo;t create a new one with the same name. So nothing will work in this case.\nPrivate Registry # If you need to use a private registry, you need to be sure to have anything required to login installed inside the VM. In my case, I\u0026rsquo;m always using GCR/GAR Registry\u0026hellip; so I needed to install the gcloud package inside the lima VM.\necho \u0026#34;deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main\u0026#34; | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list sudo apt-get install apt-transport-https ca-certificates gnupg curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y google-cloud-sdk Then just connect the docker to the desired gcloud registry:\nexport DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock gcloud auth configure-docker --quiet docker login xxxx NOTE: you surely need to login to gcloud to be able to use the private docker registry (gcloud auth login).\nPackage everything in a configuration file # Instead of configuring all the things manually everytime, you can benefit from the lima.yaml file and package everything you need. The important part is that all the command must be idempotent as they are executed anytime you restart the VM. Here an example of my script:\nprovision: # `system` is executed with the root privilege - mode: system script: | #!/bin/bash set -eux -o pipefail if ! apt list --installed | grep docker-ce; then curl -fsSL https://get.docker.com | sh - echo \u0026#39;export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock\u0026#39; \u0026gt; /etc/profile.d/docker.sh else echo \u0026#34;Docker already installed\u0026#34; fi if ! apt list --installed | grep google-cloud-sdk; then echo \u0026#34;deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main\u0026#34; | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list apt-get install apt-transport-https ca-certificates gnupg curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - apt-get update \u0026amp;\u0026amp; apt-get install -y google-cloud-sdk else echo \u0026#34;Google Cloud already installed\u0026#34; fi # `user` is executed without the root privilege - mode: user script: | #!/bin/bash set -eux -o pipefail dockerd-rootless-setuptool.sh install gcloud auth configure-docker --quiet WARNING: The first creation takes long time (several minutes depending on the Mac performances). When the lima creation seems finished, it is not. You can follow all the creation logs with a tail -f ~/.lima/default/serial.log and everything is finised when you can read\n[ OK ] Finished Execute cloud user/final scripts. [ OK ] Reached target Cloud-init target. Now you can execute all the docker commands using:\nlima docker xxxx So you can just add the correct alias on your Mac machine:\nalias docker=\u0026#34;lima docker\u0026#34; WARNING: take care because containerd and nerdctl are also installed on the machine, but they are not sharing things with the docker engine. This means if you run a container in containerd, the downloaded image cannot be seen and used by the docker-engine: the first time you start the same machine in docker, it must download it.\nCreate the full Lima VM # If you want to provision a Lima VM like the one I described, you can use my sample file.\n%[https://gist.github.com/mmornati/988cca81c5260707a453beb2d3578bd0]\nExecuting it in the following way:\nlimactl start default.yaml EDIT: 07/09/2021 Script is now updated with some interesting enhancements:\nprobes are added to wait until the full installation completion. It is done with 3 added steps: docker-ce, gcloud and user configuration The rootless docker configuration is now exposing the docker API over TCP too - mode: user script: | #!/bin/bash set -eux -o pipefail dockerd-rootless-setuptool.sh install if ! grep DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS ~/.config/systemd/user/docker.service; then /usr/bin/sed -i \u0026#39;/Environment=.*/a Environment=DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=\u0026#34;-p 0.0.0.0:2375:2375/tcp\u0026#34;\u0026#39; ~/.config/systemd/user/docker.service /usr/bin/sed -i \u0026#39;s/ExecStart=.*/ExecStart=\\/usr\\/bin\\/dockerd-rootless.sh -H tcp:\\/\\/0.0.0.0:2375/g\u0026#39; ~/.config/systemd/user/docker.service else echo \u0026#34;Docker service already configured\u0026#34; fi /usr/bin/systemctl --user daemon-reload /usr/bin/systemctl --user restart docker.service gcloud auth configure-docker --quiet The API port (2375) is automatically exposed to the host server. You can now use any docker depend app just executing export DOCKER_HOST=tcp://localhost:2375 portForwards: - guestPort: 2375 hostIP: \u0026#34;127.0.0.1\u0026#34; There is no need to create an SSH port forward with the docker.sock anymore (but it will be available too as the rootless docker is exposing the API with both two methods).\nEDIT: 08/09/2021 The latest version of the gist is tested against several project with testcontainer with success results. On the host machine we just need to configure the testcontainers/ryuk to use the correct socket.\nexport TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/run/user/502/docker.sock The UID (502) may differ in your VM. To get the correct one you can run\nlima echo \u0026#34;/run/user/$(id -u)/docker.sock\u0026#34; It won\u0026rsquo;t ever change if you won\u0026rsquo;t create your VM again and you can add in your host .zshrc or .bashrc or whatever rc file.\nIt should provide a better docker and provision experience.\n","date":"5 septembre 2021","externalUrl":null,"permalink":"/lima-vm-docker-desktop-alternative-for-macosx/","section":"Posts","summary":"","title":"Lima-VM: docker-desktop alternative for MacOSX","type":"posts"},{"content":"","date":"2 mai 2021","externalUrl":null,"permalink":"/tags/raspberry-pi/","section":"Tags","summary":"","title":"Raspberry-Pi","type":"tags"},{"content":"I used deConz in Home Assistant for the last 9 months and I discovered some crazy things in the way deConz is working. The strangest one is surely the gateway firmware update (for me a ConBee2): even if you are on a 1-year-old firmware, in Phoscon you always have the message \u0026ldquo;your version is updated\u0026rdquo;.\nThe only thing to do to really have the latest firmware version is to use the manual upgrade process, which is working in home assistant even if the official documentation says no.\nFirst of all you need to know what is the device name of your zigbee gateway. The easiest way is on the docker startup. At the beginning is showing you the list of devices and the line with the \u0026ldquo;Serial\u0026rdquo; is your gateway one (mine is /dev/ttyACM1). [s6-init] making user provided files available at /var/run/s6/etc...exited 0. [s6-init] ensuring user provided files have correct perms...exited 0. [fix-attrs.d] applying ownership \u0026amp; permissions fixes... [fix-attrs.d] done. [cont-init.d] executing container initialization scripts... [cont-init.d] firmware.sh: executing... [20:26:11] INFO: GCFFlasher V3_17 (c) dresden elektronik ingenieurtechnik gmbh Path | Vendor | Product | Serial | Type -----------------+--------+---------+------------+------- /dev/ttyACM0 | 0x1CF1 | 0x0030 | | ConBee II /dev/ttyACM1 | 0x1CF1 | 0x0030 | DE111111111 | ConBee II /dev/ttyAMA0 | 0x0000 | 0x0000 | | RaspBee Stop the deConz Integration. You can do it on the interface\u0026hellip; NOTE If you have \u0026lsquo;Watchdog\u0026rsquo; activated it may be better to disable it on the integration during this process. It could start the integration up unattended. SSH to hass (standard SSH, not the OS one). Start the deConz container to use the Gateway flasher binary. Here you should specify the device retrieved to step 1 and you should override the entrypoint which is configured to start the deConz app. docker run --rm -ti --privileged=true --device /dev/ttyACM1:/dev/ttyACM1 -v /dev/bus/usb:/dev/bus/usb --entrypoint /bin/bash homeassistant/aarch64-addon-deconz:6.8.0 Download the latest firmware , if it is not already contained in the Docker image (folder /usr/share/deCONZ/firmware/). wget http://deconz.dresden-elektronik.de/deconz-firmware/deCONZ_ConBeeII_0x266b0700.bin.GCF NOTE The firmware in this example is the latest available released on 29/04/2021. If you have a different Gateway from Dresden Elektronik, you can find the firmware at the same URL 6. Upgrade the firmware with the following command\nGCFFlasher_internal -d /dev/ttyACM1 -x 3 -f deCONZ_ConBeeII_0x266b0700.bin.GCF The -d parameter correspond to the device name found on the first step. The -x parameter is the log level. With value 3 you should have lot of details The -f is where you should provide the firmware filename. Waiting only a few seconds and you should have the firmware upgrade message.\nroot@5b3be22b1c2c:/# GCFFlasher_internal -d /dev/ttyACM1 -x 3 -f deCONZ_ConBeeII_0x266b0700.bin.GCF GCFFlasher V3_17 (c) dresden elektronik ingenieurtechnik gmbh 18:25:22:294 using firmware file: deCONZ_ConBeeII_0x266b0700.bin.GCF 18:25:22:348 ls dev: /dev/ttyACM0 (0x0000/0x0000) sn: 18:25:22:348 ls dev: /dev/ttyACM1 (0x0000/0x0000) sn: 18:25:22:349 ls dev: /dev/ttyAMA0 (0x0000/0x0000) sn: Reboot device /dev/ttyACM1 (ConBee II) 18:25:23:362 query bootloader v1 ID after 1006 ms 18:25:23:863 close serial after 1507 18:25:24:874 query bootloader v3 ID after 2517 ms 18:25:24:874 TX c081027dffc0 18:25:25:375 close serial after 3018 18:25:25:385 query deCONZ firmware version 18:25:25:387 SLIP RX frame length: 9 deCONZ firmware version 26680700 18:25:25:388 send watchdog reset 2 seconds 18:25:25:388 TX c00b02000c0005002602000000baffc0 18:25:25:390 set watchdog ttl status: 0x00 18:25:25:395 wait reboot: 2100 ms 18:25:29:500 query bootloader v1 ID after 2004 ms 18:25:29:512 RX 60 bytes ASCII R21B18 Bootloader Vers: 2.07 build: Jun 17 2019 , 08:48:53 after 2016 ms 18:25:29:513 bootloader start after 2016 ms R21B18 Bootloader Vers: 2.07 build: Jun 17 2019 18:25:29:515 GCF_ResetDeviceDone 18:25:29:517 bootloader v1 update firmware flashing 164377 bytes: |==============================| verify: . SUCCESS Wait 10 seconds until application starts 18:25:49:051 verify application is running 18:25:49:063 query deCONZ firmware version 18:25:49:065 SLIP RX frame length: 9 deCONZ firmware version 266B0700 18:25:49:065 ok, application is running root@5b3be22b1c2c:/# exit Get back to the interface and start the integration as usual to bring back your zigbee network to normal. ","date":"2 mai 2021","externalUrl":null,"permalink":"/upgrade-conbee2-firmware-in-hassos/","section":"Posts","summary":"","title":"Upgrade ConBee2 Firmware in HassOS","type":"posts"},{"content":"I started with Home Assistant over RPi more or less 3 years ago. The reason was simple: I needed to add protocols for what I was using at home and in the Smart Home Box universe you should choose between a few protocols or an incredible price (and maybe anyway not all the protocols you want to use). At the beginning, my Hass was controlling only a couple of ZWave devices and now, after we move into a new house 8 months ago, everything is under the HomeAssistant control. This means no more external custom doomed boxes.\nWhy I spent time centralizing? # The first reason: when you start adding smart devices you will have after a while a great number of custom applications to control each of them. Philips Hue, iRobot, Somfy, Netatmo, Xiaomi, Konyks, \u0026hellip; that\u0026rsquo;s crazy.\nThere is a second one: when we moved into the new house I started adding a lot of lights and accessories on the philips hue bridge. Quickly I had problems on some accessories disconnected or with an incredible lag answering to a command. On the Hue website there is information I didn\u0026rsquo;t see before: you can add up to 12 devices to the Hue Bridge. 12 devices are really nothing! You have switches (!), movement captors, temperature captors, dry captors, \u0026hellip; and only with switches I had more than 12 🤬\nSo I moved my Philips Hue installation to Hass via the ConBeeII key\u0026hellip; that\u0026rsquo;s rock!\nSo why more improvements? # There is a bad thing in the \u0026ldquo;standard\u0026rdquo; RPi installation: the SD Card. It is not a device to use when you have a lot of writes\u0026hellip; and Hass is writing a lot on the disk for logging, database information, \u0026hellip; And when the lifecycle of the SD Card is ended it simply breakup and you will lose your SmartHome box. And another fact is about performances: SD Card compared to a standard SSD is very slow in IO. I didn\u0026rsquo;t take time for a test on my configuration, but there are tons of posts on the net showing this. Just proposing one.\nThe Geek Kit # Looking on the net, it seems that when you want to pimp up your RPi there is a leader on that: Geekworm. You have lot of devices on their website to add the UPS \u0026ldquo;function\u0026rdquo;, connect a disk, a bigger box, \u0026hellip; The only \u0026ldquo;bad thing\u0026rdquo; is that you don\u0026rsquo;t have any documentation with devices, but in the end, who cares when you have internet? 😁\nI chose my kit in a couple of days especially checking a very important thing: the availability 😜 And I went for the X857-C3\nThat\u0026rsquo;s in the end quite impressive\u0026hellip; but I think nevermind the kit, you will always have a similar result: a module for the power management/UPS (with or without a battery kit), a module for the external disk (SATA or mSATA) and the external case.\nAssembling it, with the proper video is quite simple\u0026hellip; even without it is not so complex, but it is important to know where cables must be linked to the power supply board\u0026hellip; and that\u0026rsquo;s not easy without the proper board documentation.\n92850364/loLx7SZtB.jpeg)\nInstalling HassIO # Then the OS installation part: how can I move my operating system from the SD to the SSD without losing all the configuration\u0026hellip; adding back all the devices, which are now more than one hundred, it is definitely not a thing I want to do 😁 But Hass really rocks on this you will be ready to go in a really few minutes (less than 5 in my case).\nStart the RPi4 with a rasbian on an SD card. Upgrade the EEPROM to have the final bootloader version (you need it to select the proper boot order). Just check the official documentation for it or any blog post on the net. Install a fresh HassOS version on the SSD (usually /dev/sda in your RPi4). The Hass documentation show you how to install using Balena Etcher but this requires that you can connect the SSD to your laptop. In my case the mSATA via USB was not working\u0026hellip; so I just made it on the CLI in Raspbian. xz -dc hassos.img.xz | sudo dd of=/dev/sda Remove the SD Card and reboot. If all the previous steps were good, you will have a new Hass installation Copy from the original SDCard the latest snapshot (it is maybe important to make a fresh one just before starting this procedure as Step0!) into the /backup folder. Browse the Hass web interface into the snapshot restore page and you should see the copied one. Select it and restore\u0026hellip; On the SSD it is taking very few seconds for this operation\u0026hellip; then the HassIO will reboot and you will get back your SmartHome Box! Known problems\u0026hellip; # Known right now, but I spent a little bit (too much) to figure out why. The ConBeeII Key linked to the RPi4 USB is not working due to interferences with the mSATA disk (or SATA seems the same problem) USB3 port where I linked the external Disk. The problem, as explained in the Intel Whitepaper, is related to the USB3 technical design. You have some information on the deconz GitHub repo. The solution is just to move the ConBee2 stick away from the RPi, with a long USB cable. In the end, my installation looks the following\u0026hellip; a bit crazy but it is working.\nHope this could help someone of you installing and configuring a secured smart home box. Feel free to contact me if you have any kind of questions or doubt about the procedure.\n","date":"24 avril 2021","externalUrl":null,"permalink":"/home-assistant-with-rpi4-improvements/","section":"Posts","summary":"","title":"Home Assistant with RPi4 - Improvements","type":"posts"},{"content":"","date":"11 novembre 2020","externalUrl":null,"permalink":"/tags/engineering/","section":"Tags","summary":"","title":"Engineering","type":"tags"},{"content":"","date":"11 novembre 2020","externalUrl":null,"permalink":"/tags/it/","section":"Tags","summary":"","title":"It","type":"tags"},{"content":"I\u0026rsquo;ve been working in IT tech since\u0026hellip; ever. I started too young, and I started because I like to learn new things. And that what drives my life. During all these years I spent time to understand how things were working; learning, because things are changing every day; teaching to new guys what I learned,\u0026hellip; and trust me, it is not so easy as you can think. An IT guy is not (only) the person able to fix the problem on your printer; there are several different jobs and anyone required a specific skill. And personally, I\u0026rsquo;m not sure I will be able to fix your printer problem 😜\nI decided to write this article because in my career I\u0026rsquo;ve always met people thinking that what we were doing was too easy (or at least it was my feeling)\nWhat? I cannot believe that adding this function will take 2 weeks\nSystem is failing again, why you didn\u0026rsquo;t fix the problem?\nOh yes, I didn\u0026rsquo;t remember to say to you. Tomorrow we planned to do XXX that will bring 2 times traffic than usual on our systems.\nI could spend all the article writing examples like these, and I\u0026rsquo;m pretty sure, if you are reading here and you are working in IT, you are already thinking about something that happened to you. But the goal is to show you, with everyday examples, all the things IT engineers are taking care of in their job and why we must trust what they are doing.\nSlow Computer # You have a PC or Mac or again a mobile phone. I\u0026rsquo;m sure you have. And why after years you are spending money to change it? \u0026ldquo;Oh damn, it is very slow today, what is happening?\u0026rdquo;.\nThis morning, as usual, you wanted to move the latest photos in your catalog and make light/brightness changes to some of them. Arrived at your desk with your coffee, opened LightRoom (NDA Sorry Adobe :)) and wait\u0026hellip; wait\u0026hellip; wait\u0026hellip; What are doing here?\nClose all other opened programs on your PC/Mac Reboot it\u0026hellip; usually it helps 😂 Clean files? Call a friend? Then you realize that just yesterday you updated your Mac. Maybe it is related but\u0026hellip; what can you do? So, globally you could spend the whole day trying to fix your computer and without doing the activity you planned. Slowness reasons for computers are sometimes quite tricky to discover.\nNot enough disk space # How many times in your life you had a problem like this one?\nThe PowerPoint presentation for your boss is almost finished. Just save it and make a complete review in the afternoon\u0026hellip;. \u0026ldquo;Error saving the document. Not enough disk space\u0026rdquo;. 😱 Oh damn once again. As this problem is something you know, you directly think to a solution:\nEmpty your trash. Check and clean your download folder If it is not enough, clean everything you can because you really need to save it. Oh wait, you have a USB stick. That\u0026rsquo;s great. Save a copy there and you will check about the disk space later. In the afternoon you open back your presentation and\u0026hellip; WHAT??? Why you cannot open your file anymore? Yes, sometimes happens. Disks, nevermind the kind of your disk, are failing and you lose your data. If it was the only copy of your presentation you had, I think you should spend the whole night rewriting it\u0026hellip; I\u0026rsquo;m so sorry for you.\nInternet Connection # Internet: the thing which is saving our workdays in this LockDown period. We are in the connection ERA and we are spending most of our days connected. Social networking, email, news, \u0026hellip; But do we know everything about it?\nMonday morning, 10 o\u0026rsquo;clock, all the family is at home because schools are closed and you are going to start your video meeting. One minute after you began you are completely unable to understand what the person in front of you is saying: it is laggy, the audio is cut, the video is stuck most of the time. What are your actions?\nStop the video keeping only the audio part. It helped yesterday\u0026hellip; but unfortunately not today. \u0026ldquo;Wait, I hang up and recall you in a second\u0026rdquo;. The \u0026ldquo;magic recall fix\u0026rdquo;. It is the new era reboot 😎 Reboot your internet provider box? Reboot your wifi router? Oh yes. Share the 4G/5G connection from your phone. During the launch time, you discover that your 3 children and your wife were looking to Netflix series altogether. That was the problem !! Your internet is not too fast to manage all these things at once. As you will have another call in the afternoon you ask all of them to please stop using Netflix between 2 and 3 o\u0026rsquo;clock. Then you will start your call but\u0026hellip; once again nothing is working. \u0026ldquo;HEY GUYS STOP USING INTERNET!! 😠\u0026rdquo;. They listened to you and they were not watching Netflix, but it is not the only thing which is largely using your internet connection: online gaming, youtube video, social network sharing/reading, IP TV, \u0026hellip;\nYou had an idea on how to fix the problem you found in the morning, but the afternoon one was a little bit different. Bringing you to the same failing result.\nAnd what about when the problem is occurring on the internet service provider side? You lost the connection for un unknown reasons but it is not a thing you can control by yourself. The only thing you can do is call the ISP call center and: \u0026ldquo;Can you check your cable, please?\u0026rdquo; \u0026ldquo;Can we try to reboot the box? Is the left light always red?\u0026rdquo; \u0026ldquo;We are sorry, all seems working on our side and we are unable to identify the problem. A technical guy will come to you in 2 weeks!\u0026rdquo;.\n2 weeks when you are smart working 🤬\u0026hellip; that\u0026rsquo;s real life.\nPower # What is working without electricity (batteries or wired) today? I think it will be easy to explain this part. What do we do when you have an electrical problem?\nCheck if your network was hardly loaded and shut down things. Check where it is down? Is it on my side? Is it everywhere in my street? And then? What about when you discovered the problem is coming from outside? You can call your electricity provider and then\u0026hellip; wait :( Usually here are not 2 weeks because we are unable to live without electricity today. IT Engineer (hard) Job # IT Engineers are taking care to all these things at once. At least onces working on internet/network-connected systems.\nImagine an e-commerce website. What is the one you know? Amazon. Yeah for sure 🤑. IT guys at Amazon should regularly check all these things. But Amazon is not working on a system like the PC or tablet you are using to read this article. You can imagine thousands PC like the one you are using (and you are far from the reality anyway) and all of them can fail for one of the reasons we talked about. Your Bank website? The same.\nIT engineers spent the time to create/configure monitoring tools: they want to know before a problem is happening. They need to prevent problems to have systems working.\nWe can get back to the disk space example. You discovered you were out of space when you tried to save your document. How long does it take to find a solution? 2 minutes? 5 minutes? You had time for that and you were the only user on the PC. When you have thousands of computers that could run out of disk space, how to know which computer is the failing one? And if you are not \u0026ldquo;monitoring\u0026rdquo; your computers, you may know that \u0026ldquo;something is not working good\u0026rdquo; but you don\u0026rsquo;t know exactly why. You need to check each of them to find that the problem is the missing disk space. How long it will take to check any of the thousands computers? Hours? Days? But you know, we were working at Amazon. How many customers are trying to buy during these hours/days? How much money you are losing because customers are not able to buy?\nDefinitely, you cannot wait for a problem to fix it; an IT guy is imagining all the problems and find a solution even before the problem occurs.\nSometimes, as we have seen in the internet connection example, the problem is not coming in the same way you planned or you already found. You thought about the possible problem, and you know how to fix it when it will occur (do not use Netflix during a video call, for example) but the system is failing (and nobody is using Netflix). So what? At this time you have to find a solution\u0026hellip; and before you can find the solution you need to understand what is the problem. And time is running out\u0026hellip;\nAs we have seen, problems can come from things you are not able to control. In the examples, we talked about the electrical system in your street or the internet provider failure. It is the same on the systems the IT engineers are working on\u0026hellip; we can have electrical and internet problems. But not only. Systems are very complex, do you remember the thousands of computer? Each one with a cable connected to the internet box (it is quite this 😂), each one with electrical cable, a disk, \u0026hellip;\nDo you know where are all these computers today? Usually, not where the IT engineers are working. They cannot check the cables by themself. They cannot reboot the internet box using the power off button. Have you ever heard of Cloud Computing? Yeah, most of the IT Engineers computers today are moving to the Cloud. Somewhere, but we don\u0026rsquo;t exactly know where (it is not always too important), there is a place where these thousands of computers are located, linked to the internet and to the power. And just there you will find other IT engineers doing exactly the same job to ensure that the thousands of computers work.\nThen you have IT engineers which are installing software on these computers and will ensure that it is always working as expected. Which is not slowing down, as the photo catalog; it is always secured, because hackers are out there spending time to find holes in computers. You will find IT engineers at different levels of a system, each one with a specific role and knowledge. And at a moment, you will meet even the one able to repair your printer.\nConclusion # I don\u0026rsquo;t know if I reached my goal and if it is much more simple to understand what we are doing to provide an IT service (even to allow you to read this article there is a computer someone is working on!). Yes, because I\u0026rsquo;m one of those engineers working on an e-commerce system. I know sometimes is frustrating to see that we can\u0026rsquo;t do what we planned because \u0026ldquo;a system is failing\u0026rdquo;, but now you know. When a system is failing you surely have, somewhere in the world, a group of IT Engineers hardly working to try to find a solution, because customer satisfaction is the first thing matter. The system is failing because something they didn\u0026rsquo;t plan happened; because something they didn\u0026rsquo;t monitor failed; \u0026hellip; All these engineers are learning new things at this time; they are learning how to prevent the same problem in the future. So when the system is failing again it is not because they didn\u0026rsquo;t do their job (yes sometimes this happens too, unfortunately). Something new happened. A new challenge and other things to learn to be better.\n","date":"11 novembre 2020","externalUrl":null,"permalink":"/the-hard-job-of-it-engineers-for-dummies/","section":"Posts","summary":"","title":"The (hard) job of IT engineers for dummies","type":"posts"},{"content":" Qubino ZMNHJD1 and Home Assistant installation # The first device added to my Smart Home installation after the addition of the ZWave protocol is the Qubino ZMNHJD1module.\nThis is a pilot wire module for an electric radiator working on the ZWave+ network, which means you can add a temperature sensor and use it to script the radiator control. You can link this module to an electric radiator with 4 or 6 orders.\nThe Qubino will allow the remote control (via the ZWave signal) this part of your radiator and not directly the temperature… yes, for sure based on the order you can change the temperature.\nINSTALLATION # The global installation is really easy and the instructions you have with the Qubino module or internet can help you to reach your goal.\nIn the end, just link the power to the Qubino module on the “L” and “N” connectors and then the Pilote Wire to the “Q” one.\nYeah I know, I didn’t use the right cables… but I had nothing else at home and I wanted to use it as soon as possible. In my case, I even added to the temperature sensor ZMNHEA1 to the module.\nHOME ASSISTANT CONFIGURATION # Once completed you can synchronize the module and your ZWave installation simply with a click on the module button for 5 seconds (all the instructions are inside the module itself) and then a similar operation on your ZWave “server” (home assistant in my case).\nClick on the Add Node Secure button in the ZWave configuration section to start the modules search. If all worked well you will find the new Qubino device in the list of linked nodes.\nQUBINO CONFIGURATION # The last remaining thing is the configuration to allow the usage through HAssio of your electric radiator. The list of available sensors and commands, visible in the node panel of the Qubino device, should be like the following one:\npilot_wire_level: it is the value actually configured on the pilot wire which allows to identify the order set. pilot_wire_switch: used to set the desired order sending a correct value on the pilot wire pilot_wire_temperature: if you linked the temperature sensor, you will find the temperature value checking on this sensor The other controls are useful only if you linked your qubino using the optional “buttons”\nYes, but what are the correct values to set for each order? In the following table, available on the net, you can imagine what do to…\nMaybe… Ok, definitely not easy to know which are the right values. In my case, I spent several hours before to know what to put in the configuration (and once again, the internet was my friend) and a couple of months to be sure about the configuration because I installed it at the end of the summer and the radiator wasn’t working yet.\nHere what I configured for my 6 orders radiator, into the automations.yaml file.\n- alias: Set Qubino to Comfort initial_state: \u0026#39;off\u0026#39; trigger: platform: state entity_id: input_select.qubino to: \u0026#39;Comfort\u0026#39; action: service: light.turn_on entity_id: light.qubino_zmnhjd1_flush_dimmer_pilot_wire_level data: brightness: 100 id: 322e1962112842dab4defab990286212 - alias: Set Qubino to Comfort -1 initial_state: \u0026#39;off\u0026#39; trigger: platform: state entity_id: input_select.qubino to: \u0026#39;Comfort -1\u0026#39; action: service: light.turn_on entity_id: light.qubino_zmnhjd1_flush_dimmer_pilot_wire_level data: brightness: 45 id: bb19039062934ca5ba4f26ead890b4ee - alias: Set Qubino to Comfort -2 initial_state: \u0026#39;off\u0026#39; trigger: platform: state entity_id: input_select.qubino to: \u0026#39;Comfort -2\u0026#39; action: service: light.turn_on entity_id: light.qubino_zmnhjd1_flush_dimmer_pilot_wire_level data: brightness: 35 id: ee3069bd1f16476ea33ff4b1a875575a - alias: Set Qubino to Eco initial_state: \u0026#39;off\u0026#39; trigger: platform: state entity_id: input_select.qubino to: \u0026#39;Eco\u0026#39; action: service: light.turn_on entity_id: light.qubino_zmnhjd1_flush_dimmer_pilot_wire_level data: brightness: 25 id: 8397b8f4cccd4dca90996ba38e760ba4 - alias: Set Qubino to Anti Freeze initial_state: \u0026#39;off\u0026#39; trigger: platform: state entity_id: input_select.qubino to: \u0026#39;Anti Freeze\u0026#39; action: service: light.turn_on entity_id: light.qubino_zmnhjd1_flush_dimmer_pilot_wire_level data: brightness: 15 id: 04d972cb89ba4fce96b669c95e4e4e48 - alias: Set Qubino to Stop initial_state: \u0026#39;off\u0026#39; trigger: platform: state entity_id: input_select.qubino to: \u0026#39;Stop\u0026#39; action: service: light.turn_off entity_id: light.qubino_zmnhjd1_flush_dimmer_pilot_wire_level data: brightness: 0 id: 9d3e9fcf237449eaac1bd771e1509b0b I then add a simple input to allow a quick configuration of the radiator status:\ninput_select: qubino: name: Qubino Modes options: - Off - Anti-Freeze - Eco - Comfort -2 - Comfort -1 - Comfort initial: Comfort Which is giving as result a combo box with the allowed values for your Qubino.\nAnd then for sure, you can add the temperature sensor somewhere in your interface. A complete configuration using all the commands can be something like the following:\nIf you want you can find the whole configuration I’m using at home on my GitHub repository: https://github.com/mmornati/home-assistant-config\nAre you ready for winter? :)\nOriginally published at https://blog.mornati.net on November 4, 2018.\n","date":"3 novembre 2018","externalUrl":null,"permalink":"/qubino-zmnhjd1-and-home-assistant-installation/","section":"Posts","summary":"","title":"Qubino ZMNHJD1 and Home Assistant installation","type":"posts"},{"content":" Install Z-Wave Plus Z-Stick GEN5 — Aeon Labs in Home Assistant (RPi version) # In this post, you can find how you can configure a Z-Wave device in your Home Assistant smart home controller and how you can then create your network adding new Z-Wave devices.\nSome months ago I decided to look for a Smart Home box to be ready to add devices to my home (until there I had only Philips Hue devices). In the end, I choose (because I’m a geek? :)) Home Assistant: it is OpenSource and easily extensible just adding devices on the computer on which you installed HAssio (in my case is my old RPi 2).\nWinter is coming and I started making my home climate-smart: I have a central boiler and for the attic (where there is my daughter room) we have an electric heater. So: let’s start with this latest one. After a long time looking around to choose which device and which protocol to use I decided to go with Z-Wave (I will talk about the heater Z-Wave device in another post) and to use it I added to my Raspberry PI a Z-Wave Stick: Z-Stick GEN5 — Aeon Labs.\nIt is automatically recognized by Home Assistant and you can configure it very quickly. Let’s check together how you can install it.\nDetect the Device name # To be ready to configure the USB stick you need before to identify the name/path of your Stick. It can be different based on your hardware.\nFirst Method\nConnect the USB Stick to the RPi\nConnect to the RPi/Hassio via SSH\nRun the dmesg command You should see an output like the following:\n[ 5.227539] cdc_acm 1-1.5:1.0: ttyACM0: USB ACM device [ 5.229467] usbcore: registered new interface driver cdc_acm [ 5.229487] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters\u0026lt;/span\u0026gt; Which is giving you the name of the USB Stick: ttyACM0 (and the device path /dev/ttyACM0\nSecond Method\nConnect the USB Stick to the RPi\nConnect to the RPi/Hassio via SSH\nRun hassio hw info. This command is giving the information of the full hw of your PC\n{ \u0026#34;result\u0026#34;: \u0026#34;ok\u0026#34;, \u0026#34;data\u0026#34;: { \u0026#34;serial\u0026#34;: [ \u0026#34;/dev/ttyAMA0\u0026#34;, \u0026#34;/dev/ttyACM0\u0026#34; ], \u0026#34;input\u0026#34;: [], \u0026#34;disk\u0026#34;: [], \u0026#34;gpio\u0026#34;: [ \u0026#34;gpiochip0\u0026#34; ], \u0026#34;audio\u0026#34;: { \u0026#34;0\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;bcm2835 - bcm2835 ALSA\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ALSA\u0026#34;, \u0026#34;devices\u0026#34;: { \u0026#34;0\u0026#34;: \u0026#34;digital audio playback\u0026#34;, \u0026#34;1\u0026#34;: \u0026#34;digital audio playback\u0026#34; } } } } }\u0026lt;/span\u0026gt; This one is giving you the list of USB devices (two in my case) so you should know before to link the USB stick the name of what you already have.\nConfigure Home Assistant # Once you have the information about your USB Stick you are ready to configure your Home Assistant installation. As you can see in the documentation, it is quite simple. Add the following line (take care only to the network_key property !) to your configuration.yml file\nzwave: usb_path: /dev/ttyACM0 debug: true network_key: \u0026#34;0xA5, 0xDC, 0x21, 0xE1, 0xB6, 0x1D, 0x26, 0xAA, 0x15, 0x47, 0xE9, 0x12, 0x9F, 0x46, 0x1D, 0x5A\u0026#34;\u0026lt;/span\u0026gt; The usb_ path is the one you discovered in the previous step.\nThe network_key is useful and recommended to secure your Z-Wave installation.\nSecurity Z-Wave devices require a network key before being added to the network using the Add Secure Node button in the Z-Wave Network Management card. You must set the network_key configuration variable to use a network key before adding these devices.\nAs shown into the documentation you can simply generate a random one with a cli command:\n$ cat /dev/urandom | tr -dc \u0026#39;0-9A-F\u0026#39; | fold -w 32 | head -n 1 | sed -e \u0026#39;s/\\(..\\)/0x\\1, /g\u0026#39; -e \u0026#39;s/, $//\u0026#39;\u0026lt;/span\u0026gt; All is done. You just need to restart your Hassio Host and all should work. You will have a new ZWave entry into the configuration menu.\nAdd a new ZWave Device # Add new devices to your Z-wave network is really easy and it can be done directly in the Hassio configuration menu.\nPut your new device in “discovery” mode: normally should be enough to start it up but, in case, there is a button on the device to force a network discover.\nThen click on Add Secure Node button into Hassio. You should see the new node(s) into the Nodes combo box.\nAll is done. Enjoy your new Z-Wave network.\nOriginally published at https://blog.mornati.net on September 11, 2018.\n","date":"10 septembre 2018","externalUrl":null,"permalink":"/install-z-wave-plus-z-stick-gen5-aeon-labs-in-home-assistant-rpi-version/","section":"Posts","summary":"","title":"Install Z-Wave Plus Z-Stick GEN5 — Aeon Labs in Home Assistant (RPi version)","type":"posts"},{"content":" Docker images and files chown # I never thought before about the Docker containers and the results the chown (change the ownership of a file) command can have on the final image. I think the chown should be forbidden (as much as possible) because it will produce bigger images than necessary.\nBut let\u0026rsquo;s review together how Docker works to understand what I just said.\nDocker image layers # As you surely know, any Docker image consists of overlapping layers\nIn the Docker official documentation, you can find\nA Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only. \\[…\\] Each layer is only a set of differences from the layer before it.\nFor any instruction contained in your Dockerfile (and in all the FROM images) you will result in an added layer. You can check the status of a specific image just executing the history command on it:\ndocker history mmornati/docker-ghostblog:1.16.0 /bin/sh -c #(nop) CMD [\u0026#34;/bin/sh\u0026#34; \u0026#34;/ghost/... 0B /bin/sh -c #(nop) VOLUME [/ghost-override] 0B /bin/sh -c #(nop) HEALTHCHECK \u0026amp;{[\u0026#34;CMD-SHE... 0B /bin/sh -c #(nop) EXPOSE 2368 0B /bin/sh -c #(nop) ENV NODE_ENV=production 0B /bin/sh -c #(nop) WORKDIR /ghost 0B /bin/sh -c #(nop) ENV HOME=/ghost 0B /bin/sh -c #(nop) USER ghost 0B /bin/sh -c chown -R ghost:ghost /ghost \u0026amp;\u0026amp; ... 108MB /bin/sh -c #(nop) COPY dir:ec27a30893731c0... 108MB /bin/sh -c addgroup -S -g 1276 ghost \u0026amp;\u0026amp; ... 5.13kB /bin/sh -c #(nop) LABEL maintainer=Marco ... 0B /bin/sh -c #(nop) CMD [\u0026#34;node\u0026#34;] 0B /bin/sh -c apk add --no-cache --virtual .b... 3.91MB /bin/sh -c #(nop) ENV YARN_VERSION=0.27.5 0B /bin/sh -c addgroup -g 1000 node \u0026amp;\u0026amp; ad... 45.6MB /bin/sh -c #(nop) ENV NODE_VERSION=6.11.3 0B /bin/sh -c #(nop) ENV NPM_CONFIG_LOGLEVEL... 0B /bin/sh -c #(nop) CMD [\u0026#34;/bin/sh\u0026#34;] 0B /bin/sh -c #(nop) ADD file:89e72bfc19e8162... 4.81MB\u0026lt;/span\u0026gt; In the output, you can see the list of commands and the disk space consumed by each one. The result should be read bottom-up: the lower layer is the first one.\nDocker copy-on-write (CoW) strategy # To manage the files in the multiple layers, Docker is using the copy on write strategy.\nCopy-on-write is a strategy of sharing and copying files for maximum efficiency. If a file or directory exists in a lower layer within the image, and another layer (including the writable layer) needs read access to it, it just uses the existing file. The first time another layer needs to modify the file (when building the image or running the container), the file is copied into that layer and modified. This minimizes I/O and the size of each of the subsequent layers. These advantages are explained in more depth below.\nSo simply, when you need to change a file contained in another layer, Docker copies the file in the layer where you are modifying it, with the result that in your final image the file is present 2 times and is taken 2 times the disk space (one per layer).\nWhat about if you need to delete a file present in another layer? Following what we just see, the file is “hidden” in the final layer because you delete it, but the disk space is taken because the file is still present in the previous layer! So it is “useless” to remove a file.\nThis is the reason why, for example, the clean all in the following command is useless if your target is to prevent waste of disk space:\nRUN yum -y install php RUN yum clean all\u0026lt;/span\u0026gt; This other one instead will produce a single layer with the PHP package installed and with the yum cache correctly cleaned.\nRUN yum -y install php \u0026amp;\u0026amp; yum clean all\u0026lt;/span\u0026gt; Docker ChOwn # What about the chown? It is following the same rules: applying change ownership to a file, for Docker means copy that file in the new layer and change the ownership. Anytime you are using it you are taking more disk space than you need. In the history I pasted before there is the example about the image I was using for this blog:\n/bin/sh -c chown -R ghost:ghost /ghost \u0026amp;\u0026amp; ... 108MB /bin/sh -c #(nop) COPY dir:ec27a30893731c0... 108MB\u0026lt;/span\u0026gt; The first line (reading bottom-top) COPY a folder into the container (1 layer) the second one is changing the ownership of the file (1 layer). Result: 108Mb the copy + 108Mb to chown. I was wasting 108Mb of space because of the chown!!\nHow to fix it? # To prevent waste of disk space we have always to think how we can reduce the number of layers. Specifically for the chown problem, in the recent versions of Docker, you can add the ownership of the files as a parameter of the COPY command ( — chown=-user-):\nCOPY --from=plugin-builder --chown=node /builder/cloudinary-store\u0026lt;/span\u0026gt; which is producing the copy and the ownership in the same command and so in a single layer.\nThe result refactoring this blog image (~270Mb-108Mb):\nIn the end, when you are building images for the production it is important to think of the disk space and to keep in mind that your goal is to reduce the number of layers.\nOriginally published at https://blog.mornati.net on November 6, 2017.\n","date":"5 novembre 2017","externalUrl":null,"permalink":"/docker-images-and-files-chown/","section":"Posts","summary":"","title":"Docker images and files chown","type":"posts"},{"content":" PushBullet notifications for Kerberos.io # Kerberos.io is a cheap video surveillance system (the cheapest on the market I think) that you can install on a RaspberryPI and using it with an (old) USB camera, an IP camera, or with the RPi camera.\nIn Kerberos.io you can then configure different kinds of notification when motion is detected.\nOne of the available methods is the WebHook and, using this configuration, I implemented an Hook project (extensible by plugins… WIP) which allows actually to have notifications on PushBullet. The information about the installation and configuration is available on the project README file.\nIf you want to make a simple test before linking the hook to Kerberos.io, or to debug if anything is not working good, you can make a post-call to the hook project using, for example, Postman\nThe result should be a Pushbullet message on all your linked devices, or on the device, you selected to notify, with the image taken by the Kerberos.io camera.\nThe plugin system of this project is already developed, and I planned to add other notifications like mail and TextMessage. Before I said it is WIP ’cause I’d like to improve this part forcing the way to develop the plugins with Interfaces and predefined functions.\nAll is Open… if you test/use it and you have any comments improvements, don’t hesitate to post comments (here or better on GitHub).\nOriginally published at https://blog.mornati.net on September 27, 2016.\n","date":"26 septembre 2017","externalUrl":null,"permalink":"/pushbullet-notifications-for-kerberos-io/","section":"Posts","summary":"","title":"PushBullet notifications for Kerberos.io","type":"posts"},{"content":"If you are using the docker-ghostblog or docker-ghostblog-cloudinary to run your blog, you can simply update to the new versions.\nSometimes you will need to upgrade the database schema, like the migration from 1.8.6 version to the 1.9.0. After the blog startup you will notice that docker is immediately shutdown and, checking into the blog log you will find a log like the following one: In this case just run the correct docker-ghostdbmigrate to upgrade your database and then start the blog.\nFull migration procedure # docker pull mmornati/docker-ghostblog-cloudinary:1.9.0 docker pull mmornati/docker-ghostdbmigrate:1.9.0 docker run -it --rm --name blogmigrate -e NODE_ENV=production -e DB_CURRENT_VERSION=1.8.6 -v /opt/ghost-blog:/ghost-override mmornati/docker-ghostdbmigrate:1.9.0 docker run -d -p 2368:2368 -e WEB_URL=http://test.blog -e SERVER_HOST=12.4.23.5 -e SERVER_PORT=4000 -e CLOUDINARY_URL=cloudinary://87237872387:aaaaaaaaaaaa@blog-mornati-net -v /opt/data:/ghost-override mmornati/docker-ghostblog-cloudinary:1.9.0 Enjoy your blog updated\u0026hellip;\n","date":"22 septembre 2017","externalUrl":null,"permalink":"/how-to-update-to-ghost-190-with-docker/","section":"Posts","summary":"","title":"How to update to Ghost 1.9.0 with Docker","type":"posts"},{"content":"If you want an all-in-one Docker for your Blog, you can use my new docker-ghostblog-cloudinary (the one used to run this blog).\nThis Docker is based on the docker-ghostblog image and it is adding a different images storage: Cloudinary. Any time you add an image, into a post, as post back image or into the blog settings area, all the images are sent directly to Cloudinary and then served through it.\nWhy you should store images somewhere else than your blog host? For me it depends only on your host and the persons visiting it. The images, or in general the media files, are often the most busy and longest-running resources to download, so it is for me better to keep them as near as possible to the final \u0026ldquo;browser\u0026rdquo;. And then we have to consider that, depending on your blog traffic, this can take lot of bandwidth and it can slow down the user experience. Ok, ok, it is not the case for my blog :) BUT\u0026hellip; the blog server is in France and there are readers from everywhere. I think this decision will allow a faster loading for anyone.\nIf you want the Docker with a pre-configured Cloudinary plugin, you can take this one.\ndocker pull mmornati/docker-ghostblog-cloudinary:1.8.6 docker run -d -p 2368:2368 -e WEB_URL=http://test.blog -e SERVER_HOST=12.4.23.5 -e SERVER_PORT=4000 -e CLOUDINARY_URL=cloudinary://87237872387:aaaaaaaaaaaa@blog-mornati-net -v /opt/data:/ghost-override mmornati/docker-ghostblog-cloudinary:1.8.6 The CLOUDINARY_URL environment variable is used to configure the plugin with the information about your Cloudinary account.\n","date":"16 septembre 2017","externalUrl":null,"permalink":"/docker-ghostblog-cloudinary-the-new-one-for-this-blog-and-for-you/","section":"Posts","summary":"","title":"docker-ghostblog-cloudinary The new one for this blog, and for you","type":"posts"},{"content":"I took time to update and fix some little things I had in mind for the Cloudinary Ghost Storage plugin, but I just to tell you everything in the right order.\n####@sethbrasile: where is the plugin creator? No idea. I tried to reach him several times (and in several ways) in these latests months without success. Because the first version of the plugin was waiting for some pull requests to be fully compliant with Ghost 1.X.\nI then check with the Ghost community if we could switch the link to the \u0026ldquo;official\u0026rdquo; plugin\u0026hellip; and if someday @sethbrasile will come back we can put it back. So, right now, the link to the plugin you can find on the Ghost Official Page is bringing to the fork hosted on my github space. And this version is updated and fixed!!\nHow can I install this version of the plugin? # This is the tricky part: as I wasn\u0026rsquo;t able to get in touch with @sethbrasile I cannot get access to the NPM library to deliver the new version. So I decided, for the moment, to change the name of the npm lib. You can find the new version here; and naturally install it via npm:\nnpm install cloudinary-store On the repository you can find all the instructions to install it on your blog.\nWhat are the news? # The exists method to check if an image is already on cloudinary is now working correctly (the API we were using was not the correct one) There is a new configuration section to manage the file names. You can check the available parameters directly on the Cloudinary API documentation: https://cloudinary.com/documentation/image_upload_api_reference#upload During the upload we use the original filename and not the browser random one The manage of the file name allow to prevent duplicated images on your Cloudinary space. You can personalize this part with the Cloudinary API parameter (random name by cloudinary, base name + random part, only the original name, override or not in case of duplicated, \u0026hellip;) You can get back to me, directly of with a github issue, if you have any ind of problem with the plugin.\n","date":"16 septembre 2017","externalUrl":null,"permalink":"/new-official-update-for-the-cloudinary-ghost-storage-plugin/","section":"Posts","summary":"","title":"New Official update for the Cloudinary Ghost Storage plugin","type":"posts"},{"content":"I\u0026rsquo;m going ahead working on the slim of the Docker Ghost Blog I created.\nI started the project to simplify the manage of my Blog and, for this reason, was keeping all the things I needed. Which is another way to say that it wasn\u0026rsquo;t so \u0026ldquo;basic\u0026rdquo; to be used by anyone.\nI was happily hurt in seeing that the Docker was pulled plenty of times: 100K+ times (which is the max count for the docker hub\u0026hellip; I don\u0026rsquo;t know exactly how many times was downloaded!).\nFor this reason I removed from this base version all the custom things I\u0026rsquo;m using in my blog, the Cloudinary storage plugin for example, providing an easy to use Docker for anyone.\nHow can I start using Ghost? # Quick and easy:\ndocker pull mmornati/docker-ghostblog:1.8.6 docker run -d -p 2368:2368 -v /opt/blog-data:/ghost-override mmornati/docker-ghostblog:1.8.6 These commands are downloading the 1.8.6 version of the docker and starting it up on the 2368 port and using the /opt/blog-data folder as blog content folder.\nInto the github README file I put some other parameter: I used them to make an automatic link to the Docker nginx-proxy. It is automatically exposing through the ports 80 and 443 all other web dockers using some environment variables.\n","date":"16 septembre 2017","externalUrl":null,"permalink":"/the-new-ghost-blog-base-docker/","section":"Posts","summary":"","title":"The new Ghost Blog base Docker","type":"posts"},{"content":"A big image is not (always) good if it contains build and development tools.\nI spent the two last days working on the Docker used for this blog following this base concept:\nThe image used during the development is maybe not the best one for the production environment.\nIn the end a sure thing was that I didn\u0026rsquo;t needed a 1GB image just to run the Ghost Blog. Into the image there were lot of useless stuffs and lot of intermediate layers. Step 1: Change the Docker base image # Before I was using the Node 6 which is the best one when you are developing a NodeJS application but it is containing lot of stuffs which are not necessary in production. Now I decided to use the alpine version of Node. What is the difference? 265Mb vs 19Mb !! Step 2: Docker Multi-stage build # Since the 17.05 version, Docker introduces the multi-stage builds: in the same Dockerfile you can now use multiple FROM statements. Each of them can use a specific image and is basically starting a new stage of build. You can then copy artifacts from 1 stage to another.\nEx: Stage 1: build front application from sources Stage 2: build java back application from source Stage 3: create the final image getting binaries from Stage 1 and Stage 2\nIn this way you can add to the Stage 1 the packages required to build and check the front application, in the Stage 2 the java utils/library/packages to build the application\u0026hellip; but in the final image (the one you want to deploy in production) you don\u0026rsquo;t need all this build tools.\nYou can check the result of this big refactor looking to this Dockerfile. The installation of Ghost, using the GhostCLI, is not correctly working in the node alpine image. For this build stage I used the standard node image, but it is not the one used in the final container.\nStep 3: Removed the Ghost database migration script # I also decided to separate the Ghost database migration script and the Ghost Blog. So far I used the migration script only 2 times, but it is taking lot of space in the blog image.\nRight now there is a new Docker we can use when a database migration is required.\ndocker run -it --rm --name blogmigrate -e NODE_ENV=production -e DB_CURRENT_VERSION=1.0.2 -v /Users/mmornati/ghost-blog-test:/ghost-override mmornati/docker-ghostdbmigrate:v1.8.6 You have naturally to use the same version of the Ghost blog you want to run !\nThe result # Impressive, isn\u0026rsquo;t it? :)\n","date":"14 septembre 2017","externalUrl":null,"permalink":"/docker-ghost-blog-slim-down/","section":"Posts","summary":"","title":"Docker Ghost Blog Slim down","type":"posts"},{"content":"New version just released with a new database migration function inside the docker.\nIn these latests weeks the Ghost team released lot of new versions and it is quite difficult to follow and keep the docker updated.\nAnyway you can find the 1.5.0 version on DockerHub.\nWorking on this new version I discovered that database should be migrated to be able to use the docker with the latest version. To simplify the migration of your \u0026ldquo;external\u0026rdquo; database I added a new command to the docker which simplify the migration.\nYou can now simply run the following command, which is starting the latest version of the docker and migrate the database.\ndocker run -it --rm --name blogtest -p 2368:2368 -e NODE_ENV=production -e DB_CURRENT_VERSION=1.0.2 -v /Users/mmornati/ghost-blog-test:/ghost-override mmornati/docker-ghostblog:v1.5.0 /ghost/migrate-database.sh You have naturally to change:\nthe -v parameter to reference your ghost external folder; the DB_CURRENT_VERSION with the version of database (the Ghost version you were using before the Docker update) the version of the new Docker you want to start (mmornati/docker-ghostblog:v1.5.0 in this exemple I\u0026rsquo;m starting the latest available at the moment I\u0026rsquo;m writing this article) If all was ok, you should have something like this Quite easy, isn\u0026rsquo;t it?\n","date":"6 août 2017","externalUrl":null,"permalink":"/docker-ghost-blog-150-released-new-migration-feature/","section":"Posts","summary":"","title":"Docker Ghost Blog 1.5.0 released - New Migration feature","type":"posts"},{"content":"With few little steps you can really improve the performances of your web page.\nI recently migrated to the latest version of Ghost Blog platform (v1.0.2 at the moment I\u0026rsquo;m writing this article) and, during the migration, due to an huge work to completely rewrite my blog theme, I decided to start back modifying Casper: the Ghost default theme adding back some basic functionalities I need (such as Google Analytics, Disqus and the CookieBar plugin). You can check the version I made here: https://github.com/mmornati/Casper\nPerforming this migration I also decided to check my page web performances using DareBoost.\nOn the first test I did locally (using ngrok) the result was not so cool: The page size was really impressive (more than 2 Mb) Some resources was missing A couple of images was loaded using the http instead of https Some security problems requiring HTTP Headers injections In this first test, considering it was done locally on my laptop, I didn\u0026rsquo;t take care a lot to the page fully load time because it could be related to my connection.\nI than just follow the list of improvements proposed by DareBoost in the generated report and with few iterations I got a pretty good final result. The home page size decrease to 947Kb. Most of the work was done on Cloudinary, which is the service I\u0026rsquo;m using to store my blog images. I will talk about the improvements to the plugin in another article, but, thanks to @aphe which made a pullrequest to use the Cloudinary Image Manipulation API, now when we are uploading images the one used on the webpage is a compressed version of the original (based on the configuration you put on the plugin). Stay tuned if you are interested on this part\u0026hellip; but if you can\u0026rsquo;t wait, the information are on this github repository\n","date":"27 juillet 2017","externalUrl":null,"permalink":"/dareboost-analyse-your-web-pages-performances-and-improve-them/","section":"Posts","summary":"","title":"DareBoost: Analyse your web pages performances and improve them","type":"posts"},{"content":"A little documentation allowing you to add the Content Security Policy and allowing Disqus to work properly.\nTo have a secured website, as it is proposed by DareBoost if you make an analysis, you have to add some HTTP Headers with security policies. One if this Header is the Content Security Policy (CSP) which allow you to block scripts, styles, medias, \u0026hellip; coming from an unknown website. It is up to you to say to the reading browser if it should execute a script (or anything else) or not both for an external or internal/inline.\nIf you are using Disqus on your website, to allow the access to all the Disqus resources you have some url to configure here:\nhttps://disqus.com https://*.disqus.com https://*.disquscdn.com I suggest the wildcards (the \u0026lsquo;*\u0026rsquo; character) because the CDN can change based on where you located and accessing the website and then because you have activated Disqus with the sub-domain associated to your account.\n\u0026lt;meta http-equiv=\u0026quot;Content-Security-Policy\u0026quot; content=\u0026quot;default-src 'self' 'unsafe-inline' www.google-analytics.com https://code.jquery.com https://disqus.com https://*.disqus.com https://*.disquscdn.com https://*.cloudinary.com http://www.gravatar.com;\u0026quot;\u0026gt; Even with this configuration you should have an error on your browser: This is raised out because the Disqus script is trying to make a javascript eval of a string retrieved from \u0026ldquo;do-not-where\u0026rdquo;. And, as you may know, it is really dangerous to execute an eval of a variable coming from outside your script !\nI make a little search on the NET and I found this one year old discussion\nhttps://disqus.com/home/discussion/channel-discussdisqus/csp_unsafe_eval/\nIt seems the problem was already identified and they planned to removed the associated code (but after 1 year is still there). In any case it seems related to a (maybe) useless feature: This file is for link affiliation on your page.\nSo actually I think we can keep the website secured and ignore the error raised by Disqus, which is working properly even with this error.\n","date":"27 juillet 2017","externalUrl":null,"permalink":"/disqus-and-content-security-policy/","section":"Posts","summary":"","title":"Disqus and Content Security Policy","type":"posts"},{"content":" HTTP2 Some Basic tests to check if it is working on your website # I didn\u0026rsquo;t know before, but today I discovered that all my dockers were already HTTP2 enabled.\nIf you want to test your server/website can simply use this online service. In the answer, you will know if your website is supporting http2 or not.\nI then try to investigate a little bit more trying to have better information about why it was working (I know, a normal person in the same situation would have said ‘Cool, nothing to do’, but… you know… I’m an engineer :)).\ncUrl # Using curl (you need a brand new version http2 enabled. If you are on OSx you can simply install it using brew) you are able to force a request using http2 protocol and check the verbose output\ncurl -vso /dev/null https://blog.mornati.net * Rebuilt URL to: https://blog.mornati.net/ * Trying 51.254.141.153... * TCP_NODELAY set * Connected to blog.mornati.net (51.254.141.153) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * successfully set certificate verify locations: * CAfile: /usr/local/etc/openssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS header, Certificate Status (22): } [5 bytes data] * TLSv1.2 (OUT), TLS handshake, Client hello (1): } [512 bytes data] * TLSv1.2 (IN), TLS handshake, Server hello (2): { [114 bytes data] * NPN, negotiated HTTP2 (h2) { [5 bytes data] * TLSv1.2 (IN), TLS handshake, Certificate (11): { [2731 bytes data] * TLSv1.2 (IN), TLS handshake, Server key exchange (12): { [589 bytes data] * TLSv1.2 (IN), TLS handshake, Server finished (14): { [4 bytes data] * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): } [70 bytes data] * TLSv1.2 (OUT), TLS change cipher, Client hello (1): } [1 bytes data] * TLSv1.2 (OUT), TLS handshake, Unknown (67): } [36 bytes data] * TLSv1.2 (OUT), TLS handshake, Finished (20): } [16 bytes data] * TLSv1.2 (IN), TLS change cipher, Client hello (1): { [1 bytes data] * TLSv1.2 (IN), TLS handshake, Finished (20): { [16 bytes data] * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server did not agree to a protocol * Server certificate: * subject: CN=blog.mornati.net * start date: Sep 30 19:51:00 2016 GMT * expire date: Dec 29 19:51:00 2016 GMT * subjectAltName: host \u0026#34;blog.mornati.net\u0026#34; matched cert\u0026#39;s \u0026#34;blog.mornati.net\u0026#34; * issuer: C=US; O=Let\u0026#39;s Encrypt; CN=Let\u0026#39;s Encrypt Authority X3 * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 } [5 bytes data] * Using Stream ID: 1 (easy handle 0x7fc0bc805200) } [5 bytes data] \u0026gt; GET / HTTP/1.1 \u0026gt; Host: blog.mornati.net \u0026gt; User-Agent: curl/7.50.3 \u0026gt; Accept: */* \u0026gt; { [5 bytes data] * Connection state changed (MAX_CONCURRENT_STREAMS updated)! } [5 bytes data] \u0026lt; HTTP/2 200 \u0026lt; server: nginx/1.11.3 \u0026lt; date: Mon, 10 Oct 2016 20:00:10 GMT \u0026lt; content-type: text/html; charset=utf-8 \u0026lt; content-length: 18269 \u0026lt; x-powered-by: Express \u0026lt; cache-control: public, max-age=0 \u0026lt; etag: W/\u0026#34;475d-WYO6IWvGpMGFIyCdpUCIeQ\u0026#34; \u0026lt; vary: Accept-Encoding \u0026lt; strict-transport-security: max-age=31536000 \u0026lt; { [3839 bytes data] * Curl_http_done: called premature == 0 * Connection #0 to host blog.mornati.net left intact\u0026lt;/span\u0026gt; All seems good. The only thing I need to investigate better is ALPN, server did not agree to a protocol. I think it is anything related to the TLS ciphers used.\nCheck your webserver for HTTP2 # The almost only thing you need for the http2 protocol is to enable it on your webserver. To check on the NGINx you can simply execute the nginx command with the**-V** parameter.\ndocker exec -it xxxxxxxxx nginx -V nginx version: nginx/1.11.3 built by gcc 4.9.2 (Debian 4.9.2-10) built with OpenSSL 1.0.1t 3 May 2016 TLS SNI support enabled configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_perl_module=dynamic --add-dynamic-module=debian/extra/njs-0.1.0/nginx --with-threads --with-stream --with-stream_ssl_module --with-stream_geoip_module=dynamic --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-cc-opt=\u0026#39;-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2\u0026#39; --with-ld-opt=\u0026#39;-Wl,-z,relro -Wl,--as-needed\u0026#39;\u0026lt;/span\u0026gt; And check if in the output you have — with-http_v2_module. Easy!\nCheck then if the http2 is enabled on the nginx server configuration file.\nTools # There are already plenty of tools online to check/use/configure the http2 protocol. The only thing we have to know is that we need to code the website in a completely different way to allow a better performance in http2. For example: before we used the javascript/css joiner and minifier. If we will keep plenty of little (minified files) without merging them we will have a better performance on our websites. There are some good documentations online about http2: https://github.com/bagder/http2-explained https://www.smashingmagazine.com/2016/02/getting-ready-for-http2/\nI made a load/benchmark tests using an http2 tool (which should only use the http2 protocol).\nAnd the same test using the ApacheBenchmark (ab)\nI’m not sure the data are really comparable… test in http2 took less with better performance. But, for what I know, it can only be the tools used for the test.\nAnyway. Let’s start using HTTP2 and prepare your servers and applications for it.\nOriginally published at https://blog.mornati.net on October 10, 2016.\n","date":"9 octobre 2016","externalUrl":null,"permalink":"/http2-some-basic-tests-to-check-if-it-is-working-on-your-website/","section":"Posts","summary":"","title":"HTTP2 Some Basic tests to check if it is working on your website","type":"posts"},{"content":" SSH command line store for OSx and Linux like mRemote # Why this project? # Using an OSx (or Linux) environment, when you need to manage a lot of servers it could be difficult to remember all the hostnames or IP addresses. On the Windows system, I used to use mRemote, but I didn’t find anything similar on OSx/Linux. That’s the idea to create a simple CLI program allowing us to store all the connection information using tags (like mRemote does).\nHow to install the project # It’s a NodeJS project and actually it is not available on the npm repository. So, the better way is downloading sources from GitHub and install globally from sources:\ngit clone https://github.com/mmornati/ssh2-keeper.git cd ssk-keeper npm install -g .\u0026lt;/span\u0026gt; The ssh2-keeper is now available as sk command on your system.\nConfiguration # After the global installation, the configuration folder is into the global node modules installation folder. You can check which one is on your system using:\nOn my system, for example, the global folder is /usr/local/lib/node_modules which means, after the ssh2-keeper installation, the configuration folder will be /usr/local/lib/node_modules/ssh2-keeper/config. Inside this latest one, you will find a default.json file and you must configure the database folder.\n{ \u0026#34;db_path\u0026#34;: \u0026#34;/Users/mmornati/ssh2-keeper\u0026#34;, \u0026#34;server_collection\u0026#34;: \u0026#34;servers\u0026#34;, \u0026#34;tag_collection\u0026#34;: \u0026#34;tags\u0026#34;, \u0026#34;default_username\u0026#34;: \u0026#34;mmornati\u0026#34;, \u0026#34;show_ssh_command\u0026#34;: true }\u0026lt;/span\u0026gt; Usage # If you globally installed the module, using sk --help allow you to retrieve the script documentation.\nsk --help Successfully connected to : ./db USAGE: node sk [OPTION1] [OPTION2]... arg1 arg2... The following options are supported: -v, --verbose Show verbose log -t, --tag Tag(s) to allow you to find server (multiple) -h, --hostname Hostname of your server -a, --admin_server If you need to connect to an Admin server to reach your target. Ex: ssh -tt pi@192.168.0.101 ssh -tt pi2@192.168.0.102 -i, --ip Server IP address. -u, --username Username to connect to your server. If empty the one in configuration file be used -o, --operation One of ADD, SEARCH or DELETE\u0026lt;/span\u0026gt; Add a new server # sk -o add -h server35.mornati.net -i 192.168.100.35 -t jenkins -t slave -t slave04 -t integration\u0026lt;/span\u0026gt; This will add a server (if the hostname it is not already present with the same hostname) with the provided parameters and tags.\nWith a line like the previous one we’ve seen, you can also update server parameters:\nsk -o add -h server35.mornati.net -i 192.168.100.39\u0026lt;/span\u0026gt; In this way, for example, if the server was already present into the database, you will update the IP address. All other information already present into the database will be kept. In the same way, you can add a new tag to the server\nsk -o add -h server35.mornati.net -t mypersonalserver\u0026lt;/span\u0026gt; will add a new tag to the server (if not already present).\nSearch # The most used function will surely be the search one. You can search a server using the hostname, which will provide you the list of data known about the server:\nsk -o search -h server20.mornati.net Successfully connected to : ./db { hostname: \u0026#39;server20.mornati.net\u0026#39;, ip: \u0026#39;192.168.100.20\u0026#39;, tags: [ \u0026#39;www\u0026#39;, \u0026#39;front\u0026#39;, \u0026#39;front02\u0026#39;, \u0026#39;preprod\u0026#39;, \u0026#39;france\u0026#39; ], _id: \u0026#39;15404e5a1b134cb289ffae0cc89968ca\u0026#39; } Added to clipboard\u0026lt;/span\u0026gt; Or you can search using tags: it will return you the intersection between all provided tags.\nsk -o search -t prod -t front -t italy Successfully connected to : ./db ssh mmornati@server1.mornati.net ssh mmornati@server2.mornati.net\u0026lt;/span\u0026gt; It will return you all the ‘front’ and ‘prod’ servers for ‘Italy’ (servers must have the 3 tags to be into the list). When the list gives you a single result, the ssh command will be directly in your clipboard: with a simple CTRL+V you can use it! :)\nSearch improved # As I said before, this one is the most used function, I created an ‘undocumented’ function allowing you to search in this way:\nsk search prod front01 italy Successfully connected to : ./db ssh mmornati@server1.mornati.net Added to clipboard\u0026lt;/span\u0026gt; So, without providing the option name. In this case:\nthe first argument is the operation (SEARCH, ADD) if it is SEARCH from the second arguments all others will be considered as tags if it is ADD the second argument is the hostname and all others are tags Tips # To initialize my work environment database I used ‘spreadsheet’ (yes, it is amazing, but it was quick!).\nI just copied hostnames and IPs I had on another document, and then, using the spreadsheet’s CONCATENATE function I was able to generate the list of ‘ADD’ commands.\nServer with proxy # If you are using an admin server between your PC and the target server (for example on the configuration environment) you can store this information into the database ( admin_server) and the ssh command sent by the ssh2-keeper will be ready to use.\nsk search preprod france front02 Successfully connected to : ./db ssh -tt mmornati@adm01.mornati.net ssh -tt mmornati@server20.mornati.net Added to clipboard\u0026lt;/span\u0026gt; Next Steps # Better management of Users (per server and tag) Add support to ssh_key per server and tags What you need to improve it :) Originally published at https://blog.mornati.net on October 9, 2016.\n","date":"8 octobre 2016","externalUrl":null,"permalink":"/ssh-command-line-store-for-osx-and-linux-like-mremote/","section":"Posts","summary":"","title":"SSH command line store for OSx and Linux like mRemote","type":"posts"},{"content":"The DockerHub website it is created by Docker to allow developers to automate the Docker images build and push the image into the Docker repository. In this way you don\u0026rsquo;t need to use your CPU time to build the image and not even your bandwidth to upload the image into the Docker repository to allow others to pull it.\nThe procedure to configure DockerHub is really simple.\n1) Create a repository pointing to your source repository (GitHub repo): and set is as an automate build. It is not necessar but in this way anytime you are pushing changes to your repository, DockerHub will build the Docker image automatically. 2) Configure then for your repository the branch and/or tag you want to auto-check for builds. In the example, which is mine ghostblog configuration, there is a \u0026ldquo;listener\u0026rdquo; on the master branch building the latest version of the Docker and a second lister checking tag. If any tag is pushed matching the RegExp into the name: /^v.[0-9.]+$/ an image is built using the same version than the one used into the tagname. Examples:\nv.0.10.1 tag triggers a build of an image tagged as v.0.10.1 update_dockerfile tag is ignored by dockerhub 3) Change your code on the associated GitHub repo and push code (and tags if needed). You can see if any build is started on the Build Details page. At the end of the build procedure (depending on your Docker could take only few seconds or hours) you will have the status of the Docker image: Or, in case of errors: You can then click on the failing build to access to the details. Here you can find the build problem and check the build log. 4) Check on the Tags page to see the available images of your docker. Here you are also able to manage them removing, for example, some old or wrong image. 5) As I said at the begging, all the images built using this method, are available on the Docker repository. This means you can simply pull the desired image/tag.\nsudo docker pull mmornati/docker-ghostblog:v0.10.0 ==INFO==: all the images are built as \u0026ldquo;fresh\u0026rdquo; image. This means that all the intermediate steps are removed for any new build. In my example it is important to have this cause, one of the step into the Dockerfile is downloading a file from internet (ghost latest version). If you keep all the build steps you can\u0026rsquo;t be sure to have the Ghost\u0026rsquo;s latest version into your Docker.\n# Install Ghost RUN \\ cd /tmp \u0026\u0026 \\ wget https://ghost.org/zip/ghost-latest.zip \u0026\u0026 \\ unzip ghost-latest.zip -d /ghost \u0026\u0026 \\ rm -f ghost-latest.zip It is easy and quick, isn\u0026rsquo;t it? :)\n","date":"3 septembre 2016","externalUrl":null,"permalink":"/dockerhub-automate-your-docker-images-build-and-push/","section":"Posts","summary":"","title":"DockerHub: automate your docker images build and push","type":"posts"},{"content":"Since the version 0.6.x of Ghost blog platform it is possible to customize, adding a module, the management of post images. By default all the images are uploaded into the server content folder, this means, for example, that you need (lot of) space on your server and you have to backup the content folder to be sure to be able to restore everything.\nWith the storage feature, you can add a module to manage images on an external service, such as Cloudinary, Amazon S3, Google Drive, \u0026hellip;\nThe \u0026ldquo;problem\u0026rdquo; is that Ghost is going faster adding every day new features, improving the code and coming out with new versions. All this modules, maintained by \u0026ldquo;non ghost devs\u0026rdquo;, are often outdated and sometimes broken if your version of ghost is newer than the one used to create the module.\nAs I\u0026rsquo;m using Cloudinary since a couple of years now I added to my blog the Cloudinary storage module\u0026hellip; and updated it for the 0.10.x version of Ghost. You can download this version from my GitHub repo. Original Author: Seth Brasile\nThe module is really simple: all the images you upload to your blog will be sent to Cloudinary and the result url is stored into the article.\nvar Promise = require('bluebird'); var cloudinary = require('cloudinary'); var util = require('util'); baseStore = require('../../../core/server/storage/base'); // TODO: Add support for private_cdn // TODO: Add support for secure_distribution // TODO: Add support for cname // TODO: Add support for cdn_subdomain // https://cloudinary.com/documentation/node_additional_topics#configuration_options function CloudinaryStore(config) { baseStore.call(this); this.config = config || {}; cloudinary.config(config); } util.inherits(CloudinaryStore, baseStore); CloudinaryStore.prototype.save = function(image) { var secure = this.config.secure || false; return new Promise(function(resolve) { cloudinary.uploader.upload(image.path, function(result) { resolve(secure ? result.secure_url : result.url); }); }); }; CloudinaryStore.prototype.delete = function(image) { return new Promise(function(resolve) { cloudinary.uploader.destroy('zombie', function(result) { resolve(result) }); }); }; CloudinaryStore.prototype.exists = function(filename) { return new Promise(function(resolve) { if (cloudinary.image(filename, { })) { resolve(true); } else { resolve(false); } }); } CloudinaryStore.prototype.serve = function() { return function (req, res, next) { next(); }; }; module.exports = CloudinaryStore; If you want to test it and if you find anything which is not working or anything we can improve/add, spot out a message and I\u0026rsquo;ll work on it :)\n","date":"31 août 2016","externalUrl":null,"permalink":"/ghost-storage-image-module-for-cloudinary/","section":"Posts","summary":"","title":"Ghost: storage image module for Cloudinary","type":"posts"},{"content":"Since its first version I\u0026rsquo;m a fanatic of Docker: I\u0026rsquo;m a developer, I was an Ops and I don\u0026rsquo;t want to dirty a server with plenty of different packages/service that will require update and maintenance.\nThe important thing from docker is that you can separate the service/server from your personal files or code: you can update your code without changing the service or vice versa. And most important, if your server will have problem, you will be able to start up everything on a new server in a couple of minutes: download all the docker containers, restore your custom code and start all up.\nWith this post I just want to share how easy can be to manage a multi site server simply using docker containers. What you I\u0026rsquo;m going to show you is how I setup my personal webserver (the one hosting this blog).\nAll my Website is based on 2 docker images (3 in reality\u0026hellip; but, you will see):\nNginx Proxy: is a really incredible automatic Docker proxy. It is able to discover new services based on virtual host and port and expose them automatically. You start your web docker, it exposes it :) Docker GhostBlog: is a Docker container I created to simplify the installation for any new Ghost version. Nginx Server: is the base image for the proxy and, used directly, allow to expose static pages. What I just need to do is: pull these 2 dockers, copy my static files and db backups, start them up and enjoy. If I want to add a new (test) service I\u0026rsquo;m working on? Put it in a docker, start it up and Nginx Proxy will expose it.\nFollowing all my (partially masked) startup commands:\nStart the Nginx Proxy, configured for HTTPS too: docker run -d --name nginx -p 80:80 -p 443:443 -v /opt/nginx/certs:/etc/nginx/certs -v /var/run/docker.sock:/tmp/docker.sock:ro mmornati/nginx-proxy Start the Nginx WebServer. Into the following command you can see the VIRTUAL_HOST environment variable, which is the one used by the Nginx Proxy to know how to expose this container. In this example all the requests coming to mornati.net, www.mornati.net, repo.mornati.net will be redirected from the proxy to this container. Inside the /opt/nginx/conf.d folder there is the nginx configuration to answer to these virtual hosts (folders with the static contents to provide) docker run -d -p 127.0.0.1:8080:80 --name web -e VIRTUAL_HOST=mornati.net,www.mornati.net,repo.mornati.net -v /opt/nginx/conf.d:/etc/nginx/conf.d -v /opt/nginx/www:/var/www/html -t nginx:1.9.5 Start the Ghost blog container. The WEB_URL, SERVER_HOST and SERVER_PORT variables are used by the ghost container, the VIRTUAL_HOST by the Nginx Proxy. docker run -d --name blog -p 127.0.0.1:2368:2368 -e WEB_URL=http://blog.mornati.net -e SERVER_HOST=0.0.0.0 -e SERVER_PORT=2368 -e VIRTUAL_HOST=blog.mornati.net -e CLOUDINARY_URL=cloudinary://xxxx@blog-mornati-net -v /opt/ghost-blog:/ghost-override mmornati/docker-ghostblog:0.10.0 With these 3 commands (in this order: the proxy should be start before the others to allow the auto detection of new services) all my \u0026ldquo;websites\u0026rdquo; will be put online. I can update services, rollback in case of errors, test new webapps without creating a (long) out of services.\n","date":"30 août 2016","externalUrl":null,"permalink":"/docker-how-to-manage-a-multi-site-webserver/","section":"Posts","summary":"","title":"Docker: how to manage a multi site webserver","type":"posts"},{"content":"The Ghost Blog Platform, which I\u0026rsquo;m using for this blog, is still a sort of beta code. It contains today plenty of new functions and you can start do everything you like (or at least everything that should be done into a blog); it is OpenSource based on NodeJS and you can easily extend it, create themes, \u0026hellip;\nThis beta status is still impacting the update process:\nyou have to check the changelog (to imagine if anything could be broken) you have to download the source code follow the update procedure (it is always the same, but you can\u0026rsquo;t known) copy the new code to your server back the content folder (just to be sure you won\u0026rsquo;t lose anything) restart everything (NodeJS server) And\u0026hellip; if nothing is working\u0026hellip; rollback following the same procedure bottom up.\nThis means you need every time at least 15 minutes when all is good\u0026hellip; or lose a couple of hours if you need to fix stuffs.\nDocker as simple solution # Using the docker container base concept: a container is immutable we can automate all this procedure. This will reduce the manual actions and, in case of rollback, you can simply restart the previous container where you still have your old ghost version.\nYou can find what I\u0026rsquo;m using for this blog on my GitHub repository.\nAs you can see into the Dockerfile, based on the NodeJS 4.5 LTS, we are always downloading the latest version of Ghost, keep the dynamic configuration which is using some environment variables and executing some cool staffs that I let you discover reading the Dockerfile :)\nThe Ghost content folder is outside your docker container, mounted as external volume; so it is not necessary to backup it during the procedure (even if I suggested \u0026lsquo;cause we never known) and, especially, you can start a new docker to test if all is good without putting your blog offline. If all is ok for you, you can than shutdown the previous Docker container and restart the new one: your blog will be offline for less than a minute.\nI let you discover everything, completing with just some examples this blog post:\nBuild the GhostBlog container\ngit clone https://github.com/mmornati/docker-ghostblog.git cd docker-ghostblog docker build -t mmornati/docker-ghostblog:0.10.0 . Start the built container\ndocker run -d --name blog -p 80:2368 -e WEB_URL=http://blog.mornati.net -e SERVER_HOST=0.0.0.0 -e SERVER_PORT=2368 -e CLOUDINARY_URL=cloudinary://11111:aaaaaa_bbbb@blog-mornati-net -v /opt/ghost-blog-content:/ghost-override mmornati/docker-ghostblog:0.10.0 Check if all is good\ndocker ps Access to your container\nhttp://localhost Download my prebuilt containers\nI\u0026rsquo;m using the DockerHub automatic build function: any time I\u0026rsquo;m changing stuffs on github, DockerHub is creating a new version of the container. All is available online, so you can simple make a docker pull to retrieve the latest version (or a specific one).\ndocker pull mmornati/docker-ghostblog ","date":"30 août 2016","externalUrl":null,"permalink":"/ghost-blog-update-made-easy-using-a-docker-container/","section":"Posts","summary":"","title":"Ghost Blog: update made easy using a docker container","type":"posts"},{"content":"One week ago, November 13th, OnePlus start updating the OnePlus 2 phones with the latest OxygenOS update: 2.1.2. You can find detail about it directly on the OnePlus forum:\nhttps://forums.oneplus.net/threads/oxygenos-2-1-2-update-will-roll-out-to-oneplus-2-today.405301/\nThe good thing, at least for me, is the CameraApp performance improvements; the really bad thing is the DualSim \u0026ldquo;Ask Every Time\u0026rdquo; problem. Changes in dual sim settings are not persisted at all, which means, every time you have to specify the Sim Card you want to use for your call.\n###Dialer Workaround To fix this problem when you are making phone calls from the phone you can just install a different dialer and use internal settings for the dual sim, ignoring the system one. You can for example use \u0026ldquo;True Phone Dialer\u0026rdquo; which adds some interesting functions for the dual Sim usage.\nSIM card selection by Contact or Contact Group Last used SIM for a contact (for example on a missing call you can automatically recall your contact with the SIM received the call) Dual SIM filter you can apply, for example, when you are linked to a bluetooth device In the end is an interesting application to use even without the native setting problem\nThis Workaround is NOT working for a (my) bluetooth car System (Renault/TomTom). If you make a call directly selecting a number from the InCar \u0026ldquo;dialer\u0026rdquo;, you are bypassing the \u0026ldquo;True Phone Dialer\u0026rdquo; and the SIM configuration will be read into the global settings. So it\u0026rsquo;s a workaround which is not working in any situation.\nI\u0026rsquo;ll explain you later how you can automatically switch the phone settings when you enter in your car. :D\nHopefully a new update will come out soon to fix and allow us to go ahead working correctly with the dual sim functionality.\n","date":"21 novembre 2015","externalUrl":null,"permalink":"/oneplus-2-double-sim-and-ask-every-time-bug-workaround/","section":"Posts","summary":"","title":"OnePlus 2 - Double SIM and \"Ask every time\" bug - Workaround","type":"posts"},{"content":"I spent the last months playing around Docker. Even if I\u0026rsquo;ve some doubt about using it in production environment and the way you need to use it on Windows and OSx (with a Linux VirtualBox VM), it is really impressive what you can do and how you can simplify your way to work.\nBut I don\u0026rsquo;t want to talk about all the pros and cons about docker, I just want to show you how I \u0026ldquo;fix\u0026rdquo; a recent problem I had using it!\nProblem: My production server runs EL7 (CentOS7) and I need the latest git package installed. No way to find an existent RPM to install and I don\u0026rsquo;t have any other ready server to use to build the RPM.\nSolution (quick and dirty): Download the sources of git directly on the production server; install all required packages to build it; make \u0026amp;\u0026amp; make install. In case of problem\u0026hellip; you know\u0026hellip; it\u0026rsquo;s the production server!!\nDocker Solution: Configure a CentOS7 docker container and use it to build the RPM (and it\u0026rsquo;s the thing I did ;) docker-gitrpm-centos7).\nAfter this I try to create a sort of dynamic docker container I can reuse to build any sort of RPMs for any sort of RedHat based platform. The result of this work is the docker-mock-rpmbuilder.\nYou just need sources+spec file or directly the SourceRPM package, run the docker selecting your target platform, and wait for the RPM.\nThe build process is based on Mock and my current implementation allows to build packages for which all dependencies are available on the on-line repositories (official + EPEL)\u0026hellip; I will try anything complex in the future ;)\nHere you are a little doc to use my project (you can find it on the github Readme file too).\nFirst off all you need to build the docker container:\ngit clone https://github.com/mmornati/docker-mock-rpmbuilder.git cd docker-mock-rpmbuilder docker build -t mmornati/mockrpmbuilder . Create working directory # To allow the import/export of created RPMs you need to create a docker volume and allow the read/write rights (or add owner) to the user builder(uid:1000).\nmkdir /tmp/rpmbuild chown -R 1000:1000 /tmp/rpmbuild In this folder you can put the src.rpms to rebuild.\nExecute the container to build RPMs # To execute the docker container and rebuild RPMs four SRPMs you can run it in this way:\ndocker run -d -e MOCK_CONFIG=epel-6-i386 -e SOURCE_RPM=git-2.3.0-1.el7.centos.src.rpm -v /tmp/rpmbuild:/rpmbuild --privileged=true mmornati/mockrpmbuilder If you don\u0026rsquo;t have the source RPMs yet, but you get spec file + sources, to build RPMs you need to start the docker container in this way:\ndocker run -d -e MOCK_CONFIG=epel-6-i386 -e SOURCES=SOURCES/git-2.3.0.tar.gz -e SPEC_FILE=SPECS/git.spec -v /tmp/rpmbuild:/rpmbuild --privileged=true mmornati/mockrpmbuilder It is important to know:\nWith spec file the build process could be longer. The reason is mock it is invoked 2 times: the first to build SRPM the second to build all other RPMS. The folders specified for SPEC_FILE, SOURCES and SOURCE_RPM env variables are relative to your mount point. This means if files are at the root of mount point you need to specify only the file name, otherwise the subfolder should be added too. (SOURCES in my example) NB: It\u0026rsquo;s important to run the container with privileged rights because mock needs the \u0026ldquo;unshare\u0026rdquo; system call to create a new mountpoint inside the process. Without this you will get this error:\nERROR: Namespace unshare failed.\nA different solution (which didn\u0026rsquo;t worked for me right now) should be to change the lxc-configuration to allow docker the right admin just for this operation. With this command: setcap cap_sys_admin+ep But I didn\u0026rsquo;t find the right way to execute it (any hint is welcome) :)\nAllowed configurations # default epel-7-x86_64 fedora-19-x86_64 fedora-20-x86_64 fedora-21-s390x fedora-rawhide-s390 epel-5-i386 fedora-19-armhfp fedora-20-armhfp fedora-21-aarch64 fedora-21-x86_64 fedora-rawhide-s390x epel-5-ppc fedora-19-i386 fedora-20-i386 fedora-21-armhfp fedora-rawhide-aarch64 fedora-rawhide-sparc epel-5-x86_64 fedora-19-ppc64 fedora-20-ppc64 fedora-21-i386 fedora-rawhide-armhfp fedora-rawhide-x86_64 epel-6-i386 fedora-19-ppc fedora-20-ppc fedora-21-ppc64 fedora-rawhide-i386 epel-6-ppc64 fedora-19-s390 fedora-20-s390 fedora-21-ppc64le fedora-rawhide-ppc64 epel-6-x86_64 fedora-19-s390x fedora-20-s390x fedora-21-s390 fedora-rawhide-ppc64le Check build state # To check the rpmbuild progress (and/or errors) you can simply check docker logs.\ndocker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f8d161e72832 mmornati/mockrpmbuilder:latest \"/build-rpm.sh\" 2 seconds ago Up 1 seconds modest_bardeen docker logs -f f8d161e72832 =\u003e Building parameters: MOCK_CONFIG: epel-6-i386 SOURCE_RPM: git-2.3.0-1.el7.centos.src.rpm INFO: mock.py version 1.2.6 starting (python version = 2.7.5)... Start: init plugins INFO: selinux disabled Finish: init plugins Start: run INFO: Start(/rpmbuild/git-2.3.0-1.el7.centos.src.rpm) Config(epel-6-i386) Start: clean chroot Finish: clean chroot Start: chroot init INFO: calling preinit hooks INFO: enabled root cache INFO: enabled yum cache Start: cleaning yum metadata Finish: cleaning yum metadata INFO: enabled ccache Mock Version: 1.2.6 INFO: Mock Version: 1.2.6 Start: yum install [....] And use Mock log files, that are created in the output dir:\nll /tmp/rpmbuild/output/ totale 188 -rw-rw-r--. 1 1000 1000 40795 21 feb 10:37 build.log -rw-rw-r--. 1 1000 1000 144994 21 feb 10:34 root.log -rw-rw-r--. 1 1000 1000 962 21 feb 10:34 state.log Output # If all worked well, you should have all the RPMs (source + binaries) availables in the configured output folder:\nll /tmp/rpmbuild/output/ totale 28076 -rw-rw-r--. 1 1000 1000 117010 21 feb 10:40 build.log -rw-rw-r--. 1 1000 mock 7941092 21 feb 10:39 git-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 5193722 21 feb 10:33 git-2.3.0-1.el6.src.rpm -rw-rw-r--. 1 1000 mock 5472 21 feb 10:39 git-all-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 24540 21 feb 10:39 git-arch-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 90668 21 feb 10:39 git-cvs-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 14123468 21 feb 10:40 git-debuginfo-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 37600 21 feb 10:39 git-email-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 240400 21 feb 10:39 git-gui-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 148940 21 feb 10:39 gitk-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 437148 21 feb 10:39 git-svn-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 145996 21 feb 10:39 gitweb-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 mock 67256 21 feb 10:39 perl-Git-2.3.0-1.el6.i686.rpm -rw-rw-r--. 1 1000 1000 147267 21 feb 10:40 root.log -rw-rw-r--. 1 1000 1000 1248 21 feb 10:40 state.log ","date":"20 février 2015","externalUrl":null,"permalink":"/docker-rpm-builder-container/","section":"Posts","summary":"","title":"Docker: RPM Builder Container","type":"posts"},{"content":"I spent some time looking around to an annoying bluetooth problem on my Android device: bluetooth devices (smartwatch, handfree in my car, \u0026hellip;) frequently disconnect making them inusables. At the beginning I thought the problem was the custom rom I was using. So I try some others roms and I switched back to the Note2 stock rom but problem was still there.\nSurfing/searching on internet I found this post on the android project page. It seems since Android 4.4.2, Google changed the power for the bluetooth device (or it creates a sort of link between WiFi and bluetooth) and with this the bluetooth range was extremely dropped: in same case you need to keep close the two devices (less than 30cms).\nAfter some tests I found a workaround that is working for me on my Galaxy Note 2: when you need to use the bluetooth -\u0026gt; disable WiFi (simple but annoying).\nTo automate this configuration I used an automate tasker for android. Something like Tasker, AutomateIT, Atooma or any other you could know. You just need to create a very simple rule, and all will be atomatic.\nWhen Bluetooth device (X) is connected -\u0026gt; Disable WiFi When Bluetooth device (X) is disconnected -\u0026gt; Enable WiFi\nIn this way, any time I start up my car, my tasker disables the WiFi and Bluetooth works without any problem!\nI didn\u0026rsquo;t tested with Lollipop yet (there is anything official for Note2 at the moment) and I don\u0026rsquo;t know if this problem is fixed; but if you have any strange problem with your bluetooth just try to turn off the phone\u0026rsquo;s WiFi.\n","date":"5 février 2015","externalUrl":null,"permalink":"/galaxy-note-2-bluetooth-frequently-disconnects/","section":"Posts","summary":"","title":"Galaxy Note 2 - Bluetooth frequently disconnects","type":"posts"},{"content":" I recently discovered this project on the net: https://github.com/jeffdonthemic/ghost-cli\nIt allows you to make some \u0026ldquo;offline\u0026rdquo; operation on your Ghostblog. \u0026ldquo;Offline\u0026rdquo; just because there is not a real interaction with your blog; to use the script you need to export all your blog posts into a file which will be used for all operations.\nI then decided to make some changes to allow an easy way to upload ghost local images to cloudinary and I also updated the script to use the latest Ghost 0.5.2 version. You can find the forked version on my github: https://github.com/mmornati/ghost-cli\nThe two commands I added are:\ncheckcloud: to get the list of blog posts containings local images cloudid : to upload all post\u0026rsquo;s images to cloudinary and update the (offline) post. Before you begin you need to dump all blog posts (with your latest extracted file):\nbin/ghost dump You can now check all posts with \u0026ldquo;local\u0026rdquo; images:\nbin/ghost checkcloud C:/Users/mmornati/Documents/projects/ghost-cli/pages/288.md: 1 C:/Users/mmornati/Documents/projects/ghost-cli/pages/289.md: 3 Then, for any post, you can replace all local images with a cloudinary version, which will be automatic uploaded:\nbin/ghost cloudit 289.md Uploading to Cloudinary: http://blog.mornati.net//content/images/2014/Feb/visited_pages_per_day.png Uploading to Cloudinary: http://blog.mornati.net//content/images/2014/Feb/load_kb.png Uploading to Cloudinary: http://blog.mornati.net//content/images/2014/Feb/load_time_ms.png Cloudinary URL: https://res.cloudinary.com/blog-mornati-net/image/upload/v1413061309/gu1njp3kqzhagvyqdcao.png Replacing... C:\\Users\\mmornati\\Documents\\projects\\ghost-cli\\pages\\289.md Cloudinary URL: https://res.cloudinary.com/blog-mornati-net/image/upload/v1413061309/tex7fjgv2cajxqmuwuhm.png Replacing... C:\\Users\\mmornati\\Documents\\projects\\ghost-cli\\pages\\289.md Cloudinary URL: https://res.cloudinary.com/blog-mornati-net/image/upload/v1413061309/lovod5mxdhqtkyvgri47.png Replacing... C:\\Users\\mmornati\\Documents\\projects\\ghost-cli\\pages\\289.md And now you just need to update your blog post with the new content: all your images will be on cloudinary.\nIn the next version I\u0026rsquo;ll make the update automatic. The target is: new post, upload all images to cloadinary, update post into the blog. I prefer not to modify Ghost code (it will be easy to add the automatic upload to cloadinary) because they already have this feature in the dev roadmap and because I\u0026rsquo;d like to be able to update Ghost easily when a new version is released.\n","date":"10 octobre 2014","externalUrl":null,"permalink":"/ghost-blog-images-to-cloudinary-manual-script/","section":"Posts","summary":"","title":"Ghost blog images to Cloudinary: Manual Script","type":"posts"},{"content":"Today I discovered my Google Chrome was automatically updated from version 37 to version 38\u0026hellip; don\u0026rsquo;t way automatically, I don\u0026rsquo;t think I decided for this. Anyway, after this updated all websites I visited was wrong scaled with a strange blurred effected on some part of the text. For example, in GMail, all was bigger than usual, like when you select 125% scaling and when I try to write into the Hangout window, the text was blurred and impossible to read. I spent half of my day just trying to fix this problem and, because I\u0026rsquo;ve already have it in a previous version I was using in Linux, I already know it was a Chrome problem.\nTo check the real problem I just check on a page I\u0026rsquo;ve created some years ago (http://myip.mornati.net), to get the screen resolution from the browser.\nNOTE: On my laptop I\u0026rsquo;ve a 1920x1080 screen.\nChrome 38 What??? 1536x864 But\u0026hellip; WHY??\nI then tested some others browser (some = the browsers you can imagine)\nIE (yes IE too) Ok\u0026hellip; it\u0026rsquo;s a strange number\u0026hellip; but it\u0026rsquo;s IE. Anyway, the screen resolution is more similar to the normal one I had before.\nFirefox Firefox has the same problem than Chrome\u0026hellip; but, at least, all is correctly displayed at this resolution. You don\u0026rsquo;t have blur effect which make impossible to use it.\nI made a test to the Chrome\u0026rsquo;s beta version; just because I didn\u0026rsquo;t want to get back to the previous one if I cannot control the automatic update.\nChrome 39 Yes\u0026hellip; that is good and it is the value I had with the previous version.\nI spent a day to get back to a correct screen resolution in my browser and to prevent blur effect to be able to use it!!\nWhy anytime Google releases a new Chrome\u0026rsquo;s stable version there\u0026rsquo;s anything that wasn\u0026rsquo;t correctly tested?\nIf you want to fix this problem\u0026hellip; just change the chrome version without lose your time.\n","date":"9 octobre 2014","externalUrl":null,"permalink":"/google-chrome-38-wrong-display-scaling-and-blurred/","section":"Posts","summary":"","title":"Google Chrome 38: Wrong Display Scaling and blurred","type":"posts"},{"content":"I want to share with you my positive experience with the Jawbone support center (customer care) I contacted some months ago for my broken UP24 band.\nI bought my Jawbone UP24 this spring, during an USA trip and, just after 3 months of normal usage, I had problems with it. After a jogging session I noticed my band was turned off. I thought it was completely discharged (even if I was sure it wasn\u0026rsquo;t) and I then try to charge it up but\u0026hellip; when I linked the band with the official USB cable, nothing happened. It was impossible to charge it up. I then try to restart it up and check where was problem, and after a while pressing the (only) button the band started up, little light blink night/day, two vibrations and\u0026hellip; nothing more. After many tests I decided to contact the Jawbone support: I received a really fast response (just 1 hour after request) with a sort of automatic message containing the soft and hard reset instructions:\nHi Marco,\nI hope this email finds you well. Thanks for contacting Jawbone Customer Care. I’m sorry to hear your UP24 isn’t performing the way it should, but I’ll be happy to help you troubleshoot the issue you’ve experienced.\nBased on the information you’ve provided, I’d like to try a hard reset. This process returns your UP24 band to original factory settings. This reset will erase your current band data (all of your previously synced data is safe in the cloud).\nTo hard reset your band, please follow these steps:\nPress the button on your band 10 times. Try to pace the presses at about about one press every second.\nOn the 10th press, PRESS + HOLD the button for 15 FULL SECONDS, or until the sun status light appears. Once you see the light, let go of the button.\nLaunch the UP app. The band should sync automatically with the app. If the band does not sync automatically, press the button to trigger a sync.\nCompleting this reset should resolve the issues you’re experiencing with the band. Please reply here or give us a call if the difficulties persist. Your ticket number for this contact is 1887063\nOk I give another test following this, but I\u0026rsquo;ve already found these instructions on the net and didn\u0026rsquo;t worked for me. I think with all my tests I completely discharged my band and, due to the impossibility to charge it up, I cannot wen ahead with tests.\nI then rewrite a response message pointing out my problems and, without asking anything more, they propose me to change the band with a new one. Just using the band serial number the support already had information about the band size and color. I just need to confirm the ship address to receive it with the instructions to send back my band (I send it back after I received the new one!)\nI was really impressed by the incredibly efficient Jawbone support: I think it is the first support center which didn\u0026rsquo;t try to find a way to not to change the broken material.\nI have the new Jawbone UP24 since 3 months and, right now, all is ok. No wrong: all is better due to the new firmware which doubles the band\u0026rsquo;s battery life! :)\n","date":"8 octobre 2014","externalUrl":null,"permalink":"/jawbone-customer-support-jawbone-up24-replaced/","section":"Posts","summary":"","title":"Jawbone Customer Support: Jawbone UP24 replaced","type":"posts"},{"content":" Root your Note 2 # First of all, you need to prepare your computer with the Samsung Note 2 driver installed. The best way to do it is installing Kies.\nCopy the SuperSU update.zip file to your Galaxy Note 2. Now power off your phone and boot it into the Download Mode by pressing and holding Volume Down, Home and Power buttons together. Hold the buttons until the screen powers on and the warning screen appears. Then tap Volume Up key to enter Download Mode. Open Odin application. Now connect your device to the computer via USB cable. Wait until the \u0026ldquo;Added!\u0026rdquo; message appears in Odin and the ID:COM box turns light blue. Click the AP button and choose philz_touch_6.26.6-n7100.tar.md5 file. Uncheck the Auto Reboot option on Odin. Now hit the Start button to begin root installation. Wait until the installation completes and soon a PASS message with green background should appear in Odin. PhilZ CWM recovery has now been flashed to your phone. Now unplug the USB cable, remove the back cover and take out the battery. Wait for about 30 seconds before reinserting the battery. Now boot the device into recovery mode. Press and hold Volume Up, Home and Power keys together until the display turns on and the Samsung logo flickers and disappears. Now release the Power button but continue holding other keys till your Note 2 boots into PhilZ recovery mode. Browse to Install zip from sdcard option, navigate to UPDATE-SuperSU-vx.xx.zip and select it. Confirm the installation and when the file is flashed to the device, return to the main menu in recovery. Hit Reboot system button once the root installation is complete. Problem#1: If you keep getting SuperSu has stopped notifications constantly then follow these steps to fix:\nUninstall SuperSu (Dont worry you won\u0026rsquo;t lose root access) Now download SuperSu again from playstore Doing this installs SuperSu on /data/app partition where it won\u0026rsquo;t get stopped by KNOX Problem#2: If Fix no1 doesn\u0026rsquo;t work for you then try this out:\nDownload Terminal Emulator from the playstore. Launch it and type the following code: su pm disable com.sec.knox.seandroid\nNote: This will disable the Knox apps which prevent SuperSu from running\nProblem#3: If none of the above steps work to disable Knox. Then try the following:\nUsing any Root Explorer go to /system / app \u0026amp; / system / priv-app and delete all the apps having the word Knox in it. Also delete their corresponding odex files. This should work without fail. But doing this modifies your system partition and you may lose OTA functionality temporarily. Via\nInstall CyanogenMod 11 # Download the CyanogenMod version you prefer and the GoogleApps. CyanogenMod\nGApps\nCopy these two files on your phone. The target location is not important, the best is to copy both files into your phone root folder to easy access and copy speed. Restart the phone into the recovery mode (home + power + volume up when samunsung logo appear, leave the home button until the recovery app is charged) Wipe all Data, Cache and, in the advanced menu, the Dalvik Cache. Select the Install ZIP option and choose the CM installation file At the end of this installation, repete the procedure to install GApps Back to the main menu and select Reboot manu option CM 11 is now installed on your Note2 Phone.\nNote: The first startup could take several minutes\n","date":"26 septembre 2014","externalUrl":null,"permalink":"/install-cynogenmod-11-on-galaxy-note-2-n7100/","section":"Posts","summary":"","title":"Install CynogenMod 11 on Galaxy Note 2 N7100","type":"posts"},{"content":" New Galaxy Note 2\u0026hellip; and once again the auto update does not work (OTA or Kies) :S\nSome weeks ago here in France (but I suppose today is everywhere), Samsung rolled out the new Android 4.4.2 for the Galaxy Note 2 GSM (N7100) devices. If you have problem updating and/or checking for updates always return \u0026ldquo;Your device has the latest version installed\u0026rdquo; you can update it using Odin or Heimdall. The first one is simpler to use, but not available on Mac/Linux\u0026hellip; or better\u0026hellip; today there is jOdin, a Java version of Odin that works on any system. I tested it on my MacBook and, even if the devices was correctly detected, I wasn\u0026rsquo;t able to update it\u0026hellip; and I thought it was dangerous to update with a tool which works strangely.\nTo update your Galaxy Note 2 using Heimdall you can procede as following:\nDownload the firmware you want to install (the latest available for French phone is the N7100XXUFND3 XEF) Install Heimdall (if you don\u0026rsquo;t have it). Check if your phone is correctly recognized: sudo heimdall detect Download the PIT file (partition table of your phone): sudo heimdall download-pit --output /tmp/note2.pit --no-reboot Extract the tar.md5 file (rename it to tar.gz if you have problem)\nPush all the ROM\u0026rsquo;s files on your device via heimdall:\nheimdall flash --pit /tmp/note2.pit --verbose --SYSTEM system.img --BOOT boot.img --RECOVERY recovery.img --CACHE cache.img --HIDDEN hidden.img --RADIO modem.bin --TZSW tz.img --BOOTLOADER sboot.bin Wait 5/10 minutes (you can see the progress operation in heimdall) and your telephone should automatically reboot.\nNOTE: This procedure does not increment the Mod ROM Counter nor the Knox Flag. It\u0026rsquo;s an update like the Kies one.\nNOTE: With this procedure you do not lose your data.\nFirmware details # Model name: Galaxy Note 2 Model: GT-N7100 Country: France Version: Android 4.4.2 KitKat Changelist: 1280411 Build date: 8 April Product Code: XEF PDA: N7100XXUFND3 CSC: N7100OXAFND3 MODEM: N7100XXUFND3\nWhat\u0026rsquo;s new? # Smoother interface: even if the pin pad (to unlock your phone) it is smaller then before and sometime difficult to use Faster performance: I don\u0026rsquo;t have enought White status bar icons: I prefer the colored icons Full-screen album art and a camera shortcurt on the lock screen: I think this is the reason behind the smaller pin pad Wireless printing and NFC tap-to-pay support: Wireless printed was already present in the previous versions but, in this version, you can decide to desactivate the printer services you don\u0026rsquo;t use (available HP and Samsung) Options to set default messaging and launcher apps: good way yo set default apps and now you can decide to use only Hangouts for messages and not receive messages twice. Transparent status bar: it was already present in the previous version Better stability: I don\u0026rsquo;t have enough hours on this version, but right now it\u0026rsquo;s ok without a phone reset. To check the battery drain it\u0026rsquo;s seems ok but sometime I see an abnormal drain. Source\n","date":"13 mai 2014","externalUrl":null,"permalink":"/update-galaxy-note-2-to-official-android-44-with-heimdall/","section":"Posts","summary":"","title":"Update Galaxy Note 2 to Official Android 4.4 with Heimdall","type":"posts"},{"content":"Here a little doc to install a jabber server on CentOS machine.\neJabberd Installation # On your server type, as root user:\nyum -y install ejabberd Create an admin user for your server using the cli interface:\nejabberdctl register admin localhost yourpassword Give to created user admin privileges. To do this modify the /etc/ejabberd/ejabberd.cfg\n%% Admin user {acl, admin, {user, \"admin\", \"localhost\"}}. %% Hostname {hosts, [\"localhost\"]}. Now you can start the server:\n/etc/init.d/ejabberd start Create a user # From now you can execute any setting operation directly via the web interface, but\u0026hellip; I prefer the cut\u0026amp;paste way. So\u0026hellip; to add a new via cli, you can execute:\nejabberdctl register username yourdomain userpassword changing username,userpassword and yourdomain with what you prefer.\nBy now you can connect using a jabber client to the configured server. By default the configured server port is 5222.\nConfigure DNS # If you want to expose your server using an host/dns name, you should configure your dns server adding:\nDoc DNS:\n_xmpp-client._tcp.example.net. TTL IN SRV priority weight port target _xmpp-server._tcp.example.net. TTL IN SRV priority weight port target _xmpp-client._tcp.example.net. 86400 IN SRV 5 0 5222 example.net. _xmpp-server._tcp.example.net. 86400 IN SRV 5 0 5269 example.net. Now you have an xmpp-server identified for your example.net domain.\nVia: Source, Dns\n","date":"30 mars 2014","externalUrl":null,"permalink":"/install-personal-jabber-server-on-centos-using-ejabberd/","section":"Posts","summary":"","title":"Install personal Jabber Server on CentOS using ejabberd","type":"posts"},{"content":"I created a puppet module allowing you to automatically install logio server and/or harvester (logio client).\nhttps://github.com/mmornati/puppet-logio\nAs you can see into the readme file, the usage of this module is really simple.\nServer installation # To install the server you just need to include on your node definition the module \u0026lsquo;\u0026rsquo;logio::server\u0026rsquo;\u0026rsquo;. With an external node yaml classifier, for example, the configuration should be:\n--- classes: logio::server: Harvester (client) installation # To install a client you have the \u0026lsquo;\u0026rsquo;logio::harvester\u0026rsquo;\u0026rsquo; module. You can simply add this module on your node definition, with the server parameter poiting to your logio server ip address:\n--- classes: logio::harvester: logio_server: 127.0.0.1 Add Logs and/or Streams # To add a new log for your servers you can modify the templates \u0026lsquo;\u0026rsquo;templates/harvester.conf.erb\u0026rsquo;\u0026rsquo;. You can simply add a new log file path to the default stream (system) or create a new stream with logs for it. Here for example a templates including apache logs:\nexports.config = { nodeName: \"\u003c%= fqdn %\u003e\", logStreams: { system: [ \"/var/log/messages\", \"/var/log/secure\" ], apache: [ \"/var/log/httpd/access_log\", \"/var/log/httpd/error_log\", ], }, server: { host: '\u003c%= logio_server %\u003e', port: 28777 } } You can then simply access to your installed logio server, pointing your browser to:\nhttp://logio.ip.address:28778\nwhere logio.ip.address the the machine\u0026rsquo;s address or dnsname of your logio server. All harvester will then send new logs with a socket connection to your browser.\n","date":"30 mars 2014","externalUrl":null,"permalink":"/logio-puppet-modules-automatic-installation/","section":"Posts","summary":"","title":"Logio Puppet Modules: automatic installation","type":"posts"},{"content":"Twe weeks ago I migrated my blog from Wordpress to Ghost platform. Even if actually I\u0026rsquo;m loosing some useful resource/util I loved in Wordpress, Ghost it\u0026rsquo;s really awesome and easy to use.\nHere you are some screenshots taken from Google Webmaster tools.\nIt\u0026rsquo;s really impressive I think and not difficult to find the migration moment on this latest graph :D\n","date":"18 février 2014","externalUrl":null,"permalink":"/migration-wordpress-to-ghost-two-weeks-later/","section":"Posts","summary":"","title":"Migration Wordpress to Ghost: two weeks later","type":"posts"},{"content":"An important thing to be well referencend on search engines (is there anything different than Google? :D) is the sitemap.xml. In this file you should list all the pages of your website with some parameters to instruct the reader (the search engine robot for example) about the last update of a page, the update frequency and page priority.\nAfter some tests of scripts found on the net, I decided to create a specific sitemap generator for the Ghost blogging platform. The reasons is that all scripts I found and tests check for pages contactig Ghost thought the WebServer (asking for pages and checking for all links on each page). This is surely a good way to identify all pages and resources of your website automatically\u0026hellip; BUT, it could take long time to be generated.\nWith the script I created, which you can find on github repository, all pages for the sitemap file are generated simply reading the database:\nlist of published posts (draft are excluded) and static pages list of all pages (i.e. http://blog.mornati.net/page/2/) In this way your sitemap is generated in a second.\nActually the script works only for MySQL database (that is my actual installation), but with some little extensions we can add any other possible database.\nIf you execute the script without parameters an help doc is shown on the screen:\nruby generate_ghost_sitemap.rb Missing options: site, priority, frequency, destfile, hostname, user, password, dbname Usage: generate_ghost_sitemap.rb [options] -h, --help Display this screen -s, --site SITE Site base URL. EX: blog.mornati.net -f, --frequency FREQUENCY Update Frenquency. One of: always,hourly,daily,weekly,monthly,yearly,never -p, --priority PRIORITY Update priority. Values beetwen 0.0 et 1.0 -d, --destfile DESTFILE Sitemap destination file. Ex. /usr/share/server/sitemap.xml -t, --test Do not ping Google after sitemap generation -v, --verbose Verbose execution -m, --mysql HOSTNAME MySQL hostname -u, --user USERNAME MySQL Username -w, --password PASSWORD MySQL Password -b, --dbname DBNAME Database name So, to use it, you can simply add a crontab with all required parameters to access to your Ghost DB and to generate the sitemap file. The user executing cron must have access to the sitemap folder (so normally should be: root/apache/nginx).\nFor example:\n0 0 * * * /usr/bin/ruby /root/generate_ghost_sitemap.rb -s blog.mornati.net -p 0.5 -f daily -m localhost -u ghost -w mypasswd -b ghost -v -d /usr/share/nginx/ghost/sitemap.xml The script\u0026rsquo;s execution is scheduled any day at midnight with all the options you can read ;)\n","date":"10 février 2014","externalUrl":null,"permalink":"/optimize-ghost-for-seo-sitemap-generator/","section":"Posts","summary":"","title":"Optimize Ghost for SEO - SiteMap generator","type":"posts"},{"content":"TimeCapsule is the Apple (closed) backup system. But\u0026hellip; even if closed, you can configure a linux server to be your TimeMachine network disk, like TimeCapsule does.\nFirst of all you need a linux system and, to follow this step-by-step guide, you need a CentOS 6.X linux.\nInstallation # Configure EPEL repository, if your system is not yet configured with it:\nsudo rpm -Uvh http://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm Then install all the necessary services:\nsudo yum clean all sudo yum make cache sudo yum -y install netatalk avahi dbus nss-mdns ####Configuration Configure netatalk service editing the /etc/netatalk/afpd.conf file and adding the following line at the end of the file:\n- -transall -uamlist uams_randnum.so,uams_dhx.so,uams_dhx2.so -nosavepassword -advertise_ssh Create folder to use for TimeMachine backups:\nmkdir -p /mnt/data/TimeMachine chown youruser:youruser /mnt/data/TimeMachine Where youruser is a linux local user that can connect to the system (with a password) and the one you want to allow to use TimeMachine backup.\nThen edit the netatalk AppleVolumes file (/etc/netatalk/AppleVolumes.default) and add the folder you want to use for your backups:\n/mnt/data/TimeMachine allow:youruser options:usedots,upriv,tm dperm:0775 fperm:0660 cnidscheme:dbd You just need to change youruser with the previously selected user.\nNext configure the nsswitch service into the file /etc/nsswitch.conf and add the following line at the end:\nhosts: files mdns4_minimal dns mdns mdns4 In Avahi, configure the afpd service to be brodcasted on the network via the avahi daemon. Create the file /etc/avahi/services/afpd.service with the following content:\n\u0026lt;?xml version=”1.0″ standalone=’no’?\u0026gt; \u0026lt;!DOCTYPE service-group SYSTEM “avahi-service.dtd”\u0026gt; \u0026lt;service-group\u0026gt; \u0026lt;name replace-wildcards=”yes”\u003e%h\u0026lt;/name\u0026gt; \u0026lt;service\u0026gt; \u0026lt;type\u003e_afpovertcp._tcp\u0026lt;/type\u0026gt; \u0026lt;port\u003e548\u0026lt;/port\u0026gt; \u0026lt;/service\u0026gt; \u0026lt;service\u0026gt; \u0026lt;type\u003e_device-info._tcp\u0026lt;/type\u0026gt; \u0026lt;port\u003e0\u0026lt;/port\u0026gt; \u0026lt;txt-record\u003emodel=TimeCapsule\u0026lt;/txt-record\u0026gt; \u0026lt;/service\u0026gt; \u0026lt;/service-group\u0026gt; Disable the SSH service from avahi:\nmv /etc/avahi/services/ssh.service /etc/avahi/services/ssh.service.disabled If you have iptables enabled on your system, you need to open the ports used by TimeMachine. Add these lines to your /etc/sysconfig/iptables file:\n-A INPUT -p tcp -m state --state NEW -m tcp --dport 548 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 5353 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 5354 -j ACCEPT -A INPUT -p udp -m udp --dport 548 -j ACCEPT -A INPUT -p udp -m udp --dport 5353 -j ACCEPT -A INPUT -p udp -m udp --dport 5354 -j ACCEPT Reload the iptables configuration, or restart the service:\n/sbin/service iptables restart Ensable and start all service:\n/sbin/chkconfig netatalk on /sbin/chkconfig messagebus on /sbin/chkconfig avahi-daemon on /sbin/service avahi-daemon restart /sbin/service messagebus restart /sbin/service netatalk restart Going back to your Mac the disk should be visible in your TimeMachine. If not try to check services (/sbin/service xxx status) and restart them.\nEnjoy your new OpenSource (and low cost!!) TimeCapsule\n","date":"8 février 2014","externalUrl":null,"permalink":"/centos-6-as-apple-timemachine-backup/","section":"Posts","summary":"","title":"CentOS 6 as Apple TimeMachine Backup","type":"posts"},{"content":"The usage of meta keywords is today maybe not too useful: Google says that robots does not take care to this meta anymore. But, in the SEO rules, and in the SEO Wordpress plugin too, this meta information is always set.\nI decided to add it on my blog. This update requires a Ghost core change.\nEdit the core/server/helpers/index.js file and add the following functions (it does not exist), after the meta_description one:\ncoreHelpers.meta_keywords = function (options) { /*jslint unparam:true*/ var keywords, blog; if (_.isString(this.relativeUrl)) { if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '' || this.relativeUrl.match(/\\/page/)) { blog = config.theme(); keywords = ''; } else { keywords=\"\"; if (this.post \u0026\u0026 this.post.tags) { this.post.tags.forEach(function(value) { if (!keywords==\"\") { keywords+=\",\"; } keywords+=value.name; }); } } } return filters.doFilter('meta_keywords', keywords).then(function (keywords) { keywords = keywords || \"\"; return new hbs.handlebars.SafeString(keywords.trim()); }); }; This function, when you are on a post page, will check for post tags and create the meta keywords with them.\nIn the same file, at the end into the registerHelpers function, add the following line where you want (for example after the meta_title line):\nregisterAsyncThemeHelper('meta_keywords', coreHelpers.meta_keywords); Save the file.\nYou just need to use now your keywords meta into your theme. TO do this, open the default.hbs file, and, after the meta_description line, add:\n\u0026lt;meta name=\"keywords\" content=\"{{meta_keywords}}\" /\u0026gt; Save, restart the NodeJS server and enjoy your new SEO functionality.\n","date":"8 février 2014","externalUrl":null,"permalink":"/optimize-ghost-for-seo-keywords/","section":"Posts","summary":"","title":"Optimize Ghost for SEO - Keywords","type":"posts"},{"content":"The Ghost blogging platform does not actually have plugins (and maybe will always be this way). A thing I missing after migration is the SEO optimization for any blog post\u0026hellip; so I stated making it by myself.\nFirst step: change page title to respect SEO \u0026ldquo;rules\u0026rdquo;. The title of any post page should also have the blog name suffix:\n\u0026lt;title\u0026gt;Post title | Blog name\u0026lt;title\u0026gt; To accomplish this in Ghost you can proceed into two different ways:\nPersonalise the default.hbs file in your theme \u0026lt;title\u0026gt;{{meta_title}} | {{@blog.title}}\u0026lt;/title\u0026gt; Change the Ghost core to provides the meta_title variable with the correct value, allowing you to change theme without losing SEO customisations. Edit the file core/server/helpers/index.js around the line 395. Following the complete code of my meta_title function: coreHelpers.meta_title = function (options) { /*jslint unparam:true*/ var title = \"\", blog; if (_.isString(this.relativeUrl)) { if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '' || this.relativeUrl.match(/\\/page/)) { blog = config.theme(); title = blog.title; } else if (this.post) { blog = config.theme(); title = this.post.title + ' | ' + blog.title; } } return filters.doFilter('meta_title', title).then(function (title) { title = title || \"\"; return new hbs.handlebars.SafeString(title.trim()); }); }; In bold the code I changed.\nRestart your NodeJS server and enjoy your first SEO optimization.\n","date":"8 février 2014","externalUrl":null,"permalink":"/optimize-ghost-for-seo-page-title/","section":"Posts","summary":"","title":"Optimize Ghost for SEO - Page Title","type":"posts"},{"content":"After years using Wordpress for my personal blog, I decided to test something new. Wordpress works very well, but I think is became too heavy for a simple blog in the latests versions\u0026hellip; Anyway\u0026hellip; I found Ghost, a NodeJS blogging platform, that is actually in strong development but with all basic functionalities already.\nThe only thing I want to share for the moment is the performance test comparison. Sure, Wordpress does not have exactly the same theme and could surely be better\u0026hellip;\nWordpress: Ghost: ","date":"7 février 2014","externalUrl":null,"permalink":"/completed-blog-migration-from-wordpress-to-ghost/","section":"Posts","summary":"","title":"Completed blog migration: from WordPress to Ghost","type":"posts"},{"content":"As I said in my previous post, I recently decided to migrate my blog to something new and different. Even if Gost is still at the beginning of the dev, it already has all necessary basic functionalities for a blog platform.\nI want to share with you a Step-By-Step guide for migration (supposing you have already installed the Ghost platform).\nFirst of all, we need to prepare the migration:\nGhost does not actually have an integrated comment system. You should use an external service. Ghost allow you to integrate images in your posts, but it does not exactly has an image library. Migrate this way could be really long\u0026hellip; Comments # To manage comments a good service actually is Disqus, and you can import all your wordpress comments automatically. You have two differents way to do this:\nInstall the Disqus plugin on your blog, configure it with your disqus account information, and click on the export button. It seems a good system, but I tested it 3 times and only exported me 3 comments to disqus and I did\u0026rsquo;nt understand why. The second method (the one I finally used) consists into the export of the whole wordpress database using the provided admin function: Tools -\u0026gt; Export And import then this file to Disqus to have all your comments migrated:\nImages # For images you have many differents alternatives to manage a media library: Google Picasa, Flickr, directly on your WebServer, FTP, \u0026hellip; The real problem behind this migration is that you have to edit all your posts to put the new correct address for any image\u0026hellip; and it could be a huge work!! The service I choose, that is the one suggested by Ghost too, is Cloudinary. The reason is you have a Wordpress plugin you can use to export the whole media library, and the plugin also changes all the images links into posts!\nMigrate # Now you are ready to migrate to your new blog. There is a plugin that export all posts in a format you can directly re-import into Ghost. You just need to click on the Export button on the plugin page to download the export file to your PC.\nGoing to debug page of Ghost blog: http://yourblog.net/ghost/debug you can use the previously exported file to recreate your blog here.\nThat\u0026rsquo;s all.\n","date":"7 février 2014","externalUrl":null,"permalink":"/migrate-from-wordpress-to-ghost-how-to/","section":"Posts","summary":"","title":"Migrate from Wordpress to Ghost: How-To","type":"posts"},{"content":"I recently noticed that on my OVH VPS Server, SSH sessions remain blocked (appended with Write failed: Broken pipe message after a while) when I leave them unused for a while (about 30 seconds). This means you will need to reconnect once again, and all ssh/bash processes from the previous connection will be alive (waste of memory!).\nI think there is a proxy (new proxy? knowing I didn\u0026rsquo;t have this problem before) between me and my server that kill idle connections.\nTo solve this problem you can change settings on the client, adding ServerAliveInterval property in you sshd config file. If you are on Linux or Mac client, you need to edit sshd_config:\nsudo vi /etc/ssh_config Append to the end of file\nServerAliveInterval 30 which means your client sends an handshake message any 30 seconds to the server.\nIf you have a windows client you can set the KeepAlive like in the following screenshot\nNow all your ssh connection should be kept active even if you leave your computer for a coffe ;)\n","date":"11 janvier 2014","externalUrl":null,"permalink":"/ovh-vps-ssh-broken-pipe-timeout-how-to-keep-alive/","section":"Posts","summary":"","title":"OVH VPS SSH Broken Pipe (Timeout): How to keep alive","type":"posts"},{"content":"After some custom ROM tests I come back to the official Samsung ROM; the latest ROM that Samsung recently released for the \u0026ldquo;old\u0026rdquo; Note 2 phablet phone. To reinstall the official ROM I can\u0026rsquo;t use neither the OTA Upgrade nor the Kies Upgrade. The problem was the phone wasn\u0026rsquo;t recognized by Samsung service as the official one, and in any case the Android version was already the latest one. So I installed the official 4.1.2 to try to use one of the official methods, and for this I installed mobile ODIN on my phone.\nYou should proceed in this way:\ninstall mobile odin Download the official custom ROM (4.1.2 or 4.3) Copy the downloaded tar.md5 file on your phone (internal or external SD card) Chose the copied ROM within ODIN and do the installation ![unnamed](/images/galaxy-note-2-official-android-43-rom/00-unnamed_otuopm.jpg) If you decide, in odin, not to root the phone and install mobile odin with the custom rom, the process also reset the \u0026ldquo;custom rom counter\u0026rdquo; to erase any trace of phone change. To prevent any possible bug I also decide to make a full reset of data and cache memories.\nIf all worked well you should have a phone provisioned with the original Samsung ROM.\nSince I had chosen to install the 4.1.2 ROM, after the phone boot and basic configuration, I tried to make an OTA update (\u0026ldquo;You already have the latest version\u0026rdquo;) and a Kies Update (\u0026ldquo;You cannot use Kies to update your phone\u0026rdquo;). So\u0026hellip; even after all of this, nothing changed.\nNow you can proceed, I think, in two different way:\nroot the telephone, install odin, and rerun the same procedure with the new ROM (not sure what should happen with the latest Knox security introduced by Samsung) Install the standard ROM with the standard odin procedure If you have a Windows computer it's simple because you can use the \"real\" odin program, but, if like me, you just have a Mac or a Linux computer, you must use Heimdall to manually install your ROM. First of all you need to download the official Samsung ROM you want to install on your phone, and here google should help you pointing to the right file (Note 2 ROM). Then you have to connect the Phone via USB to a computer with Heimdall installed and the Samsung drivers to recognize your phone, and reboot it in the Odin Mode (Volume Down + Home + Power buttons pressed at the same time).\nCheck if the phone is recognized by your computer with\nsudo heimdall detect Extract the list of partitions from your phone to use to provision it:\nsudo heimdall download-pit --output /tmp/note2.pit --no-reboot Then extract the tar.md5 file (is a simple renamed tar.gz file) and push all the ROM\u0026rsquo;s files on your device via heimdall:\nheimdall flash --pit /tmp/note2.pit --verbose --SYSTEM system.img --BOOT boot.img --RECOVERY recovery.img --CACHE cache.img --HIDDEN hidden.img --RADIO modem.bin --TZSW tz.img --BOOTLOADER sboot.bin After a while, and if all worked well, your telephone should reset in your brand new system. It\u0026rsquo;s important to make a full rom installation (with all the partitions I listed in my command), in a different way your telephone boot but It detects you have installed a custom ROM (due to missing Knox requirements\u0026hellip; that means you must install it on your phone :(). And more, without a full installation the wireless does not correctly work.\nIf you have problems executing one, or more, heimdall\u0026rsquo;s operations (and you are using a Mac), maybe you should fix some Samsung driver problems:\nsudo kextunload -b com.devguru.driver.SamsungComposite sudo kextunload -b com.devguru.driver.SamsungComposite sudo kextunload -b com.devguru.driver.SamsungACMControl Take care, all operations should be run as root user (with sudo prepend to any command, or changing user for root with sudo su command).\nI can say that this new ROM seems more stable than the customs I tested, and, even if is not \u0026ldquo;optimized\u0026rdquo; in term of memory and/or processor usage, I have no memory problem.\n","date":"23 décembre 2013","externalUrl":null,"permalink":"/galaxy-note-2-official-android-43-rom/","section":"Posts","summary":"","title":"Galaxy Note 2 Official Android 4.3 ROM","type":"posts"},{"content":"After a week testing the Hurricane v6 ROM on my Galaxy Note 2, I decided to make a test to the official Samsung 4.3 rom for my phablet, that meanwhile was sorted out. With the custom Hurricane ROM, even if it worked well, I found some annoying bugs that led me to change the Rom once again, for the official one. Here a short list of things I didn\u0026rsquo;t like on the custom Hurricane ROM:\napplications crashes: sometimes applications crashes without any real reason. I have no memory problem, I can't see anything on the logcat, but sometimes nothing worked. To resolve this problem, the only solution I found was to reboot the phone. google account lost: occasionally, and inexplicably, the phone lost the connection with google account. To correctly use it again it was necessary to enter the credentials and wait for the complete synchronization (contacts, mail, google+, ...). Lot of time lose for nothing! phone call crashes: I think is linked with the applications crashes, but two times (in a week) the phone application crashes during a phone call. Not exactly a good thing for a device you use mainly to make phone calls ;) As you can see there was not many problems on the ROM, but I didn't like these little things. Anyway, if you want to have Note 3 functions (such as Air Command) you should use a custom ROM, like this one, because Samsung did not integrate all Note 3 functions in the official ROM. In this case, the Hurricane ROM is maybe the best one for your note 2. ","date":"22 décembre 2013","externalUrl":null,"permalink":"/hurricane-rom-short-review-before-changing-it-again/","section":"Posts","summary":"","title":"Hurricane ROM - short review before changing it (again)","type":"posts"},{"content":"After months waiting for a Samsung Update for my Note 2, which is actually still not available, I decided to make some tests on other ROMs with Android 4.3 and/or 4.4, to give a new life to my smartphone.\nThe first ROM I tested was the Cyanogenmod (unofficial) 11, with Android Kitkat, because Cyanogemod\u0026hellip; is cyanogen :D The phone worked really fast and ROM was not bad, even if I noticed some bugs making and receiving phone calls, but there are some major problems that made me decide to change the ROM once again. The problem was:\nDesktop icons not optimal for the Note 2 screen size, compering to the standard Samsung ROM. Wasn't beautiful to see and the icon grid allow less icons than the default rom. Naturally you can change this with some other mods... but I didn't test it due to other problems. S Pen is not supported. You can use it to draw/hadwrite but all other functions are not supported (you can't for example take a screenshot of the selected part of your desktop). If you don't use S Pen you can switch to Cyanogenmod, even if I think, 'if you don't use S Pen, why are you using a Galaxy Note?' ![2013-12-08 15.12.23](/images/hurricane-rom-for-galaxy-note-2-make-it-like-the-note-3/00-2013-12-08-15_12_23_kkdduo.png) ![2013-12-08 15.12.09](/images/hurricane-rom-for-galaxy-note-2-make-it-like-the-note-3/01-2013-12-08-15_12_09_ouwhgn.png) So I decided to get back to a TouchWiz Rom (TouchWiz is the Samsung Version of Android). After some search on the net (and on the xda-developer forum) I found the ROM to test: Hurricane Note 2 Rom This rom bring Android 4.3 on your Note 2 and, with another little mod, you can also add most of the Galaxy Note 3 features!!\nAfter a week using it I can say the ROM is stable (sometime I notice some phone slow reaction, but could be something linked with applications I use) and phone work fast\u0026hellip; and, the most important thing, you can going ahead using your S Pen with much more features took from Note 3. I think this ROM is really the one you should use on your Note 2 to give it a new life!\nYou can find all installation instruction on the XDA dev forum\u0026hellip; but if you have any problem or question you can ask me here.\n","date":"10 décembre 2013","externalUrl":null,"permalink":"/hurricane-rom-for-galaxy-note-2-make-it-like-the-note-3/","section":"Posts","summary":"","title":"Hurricane ROM for Galaxy Note 2: Make it like the Note 3","type":"posts"},{"content":"If you have a Samsung phone and you don\u0026rsquo;t understand where you are loosing most of your space, you can take a look to the CloudAgent Application.\nThe CloudAgent app is the one behind the Cloud menu inside the settings one\n![Screenshot_NormarAppImage](/images/samsung-cloudagent-disk-usage/00-Screenshot_NormarAppImage_xwdsoj.png)\nIf you link your DropBox account to your phone and you use it to automatically upload taken photos and videos, the cloud settings are, by default, configured to make a local backup of all camera upload images/videos. That means, after a while your cache will take lot of space on your phone (in the cloudagent/cache/root/ folder). You can check and disable this settings going into Settings -\u0026gt; Cloud menu and then selecting Pictures and Videos menus to check if the cache is activated and to disable it. When you disable it, all local (cached) files are automatically deleted (but Dropbox uploaded photos and local taken photos are not impacted!!)\n[gallery ids=\u0026ldquo;949,943,944,945,946,947,948\u0026rdquo;]\n","date":"10 décembre 2013","externalUrl":null,"permalink":"/samsung-cloudagent-disk-usage/","section":"Posts","summary":"","title":"Samsung CloudAgent disk usage","type":"posts"},{"content":"Sometimes happens I work on networks which only allow HTTP and HTTPS connection. That means I can\u0026rsquo;t connect to any external server using the SSH protocol, I can\u0026rsquo;t connect on FTP servers, \u0026hellip; A simple way to workaround this limitation, is to install a VPN service that you can reach using the standard HTTPS port; for exemple an OpenVPN server on 443 port.\nToday I want to show you the \u0026ldquo;workaround 2.0\u0026rdquo;: an SSH shell written in HTML5 you can use within any modern browser: GateOne. Following the instructions to install it on CentOS distribution (tested on CentOS6 but I think it should work with the same procedure on CentOS 5 too).\nInstall Tornado WebServer (the one used by default by GateOne)\nrpm -Uvh https://github.com/downloads/liftoff/GateOne/tornado-2.4-1.noarch.rpm Now you can install GateOne using the provided RPM package, or building it using the latest sources:\nrpm -Uvh https://github.com/downloads/liftoff/GateOne/gateone-1.1-1.noarch.rpm Even if into the RPM you have an init.d script to start gateone service, the first time you need to start it up manually using the .py file. This is just to allow GateOne to create all necessary files and SSL certificates:\npython /opt/gateone/gateone.py [W 131113 22:19:49 terminal:181] Could not import the Python Imaging Library (PIL) so images will not be displayed in the terminal [W 131113 22:19:49 gateone:2893] dtach command not found. dtach support has been disabled. [I 131113 22:19:49 gateone:2917] Connections to this server will be allowed from the following origins: 'http://localhost https://localhost http://127.0.0.1 https://127.0.0.1' [I 131113 22:19:49 gateone:2305] Using google authentication [I 131113 22:19:49 gateone:2404] Loaded plugins: bookmarks, convenience, example, help, logging, logging_plugin, mobile, notice, playback, ssh [I 131113 22:19:49 gateone:3054] Listening on https://*:443/ [I 131113 22:19:49 gate one:3060] Process running with pid 11674 Now that all files are correctly generated you can stop GateOne pressing CTRL+C and configure it before starting up the server. On CentOS rpm package, the configuration file is located in /opt/gateone/server.conf. The important thing to configure is the property origins: origins = \u0026ldquo;http://localhost;https://localhost;http://127.0.0.1;https://127.0.0.1;https://ssh.yourserver.com\u0026rdquo; where you should specified all allowed \u0026ldquo;VirtualHost\u0026rdquo; to connect to your SSH shell. In the previous setting a user cannot, for exemple, connect to the SSH HTML5 console using the public IP address of the sever (https://ip_add) but it is possible to connect using the server hostname or in local. On the GateOne documentation you could find a description for any property in this file.\nNow, if all is ok, you can startup the GateOne service and connect to your SSH console within the browser:\n/etc/init.d/gateone start chkconfig gateone on If you use a recent version of Firefox or Chrome you should be able to see the ssh prompt. Actually I see Safari does not work (due to an HTTP Sockets problem) and Internet Explorer\u0026hellip; well, is Internet Explorer\u0026hellip; Microsoft is still implementing CSS2, you should be able to use GateOne in 10 years. :)\nThere are some interesting features in GateOne. For example all SSH sessions are logged and recorded. You can use it to create documentation screencast like this one: http://www.mornati.net/GateOneDemo.html\n","date":"12 novembre 2013","externalUrl":null,"permalink":"/gateone-an-html5-ssh-shell-on-your-browser/","section":"Posts","summary":"","title":"GateOne, an HTML5 SSH shell on your browser","type":"posts"},{"content":"I was reading articles about caching systems for Wordpress, and I found many conflicting opinions: or completely pro cache or absolutely against cache framework.\nI then decided to make a simple test to verify if it was really useful to have a cache on this blog (I\u0026rsquo;ve W3 Total Cache installed since the beginning) and here you are the results\u0026hellip; NB. On my nginx web server I\u0026rsquo;ve gzip activated on both tests with a browser cache for all static files.\nWithout cache\nMacBook-Pro-di-Marco:~ mmornati$ ab -n 100 -c5 http://blog.mornati.net/ This is ApacheBench, Version 2.3 \u0026lt;$Revision: 655654 $\u0026gt; Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking blog.mornati.net (be patient).....done Server Software: nginx/1.4.3 Server Hostname: blog.mornati.net Server Port: 80 Document Path: / Document Length: 55299 bytes Concurrency Level: 5 Time taken for tests: 65.994 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 5576900 bytes HTML transferred: 5529900 bytes Requests per second: 1.52 [#/sec] (mean) Time per request: 3299.694 [ms] (mean) Time per request: 659.939 [ms] (mean, across all concurrent requests) Transfer rate: 82.53 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 173 321 79.4 317 483 Processing: 1676 2906 587.0 2845 4604 Waiting: 1068 2113 596.4 2058 3726 Total: 2038 3226 577.0 3178 4947 Percentage of the requests served within a certain time (ms) 50% 3178 66% 3424 75% 3673 80% 3720 90% 3873 95% 4160 98% 4779 99% 4947 100% 4947 (longest request) With W3C Total Cache\nMacBook-Pro-di-Marco:~ mmornati$ ab -n 100 -c5 http://blog.mornati.net/ This is ApacheBench, Version 2.3 \u0026lt;$Revision: 655654 $\u0026gt; Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking blog.mornati.net (be patient).....done Server Software: nginx/1.4.3 Server Hostname: blog.mornati.net Server Port: 80 Document Path: / Document Length: 55619 bytes Concurrency Level: 5 Time taken for tests: 2.994 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 5640878 bytes HTML transferred: 5617244 bytes Requests per second: 33.40 [#/sec] (mean) Time per request: 149.717 [ms] (mean) Time per request: 29.943 [ms] (mean, across all concurrent requests) Transfer rate: 1839.70 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 16 21 2.6 21 29 Processing: 81 125 18.5 121 186 Waiting: 34 54 15.3 52 105 Total: 105 145 18.4 141 207 Percentage of the requests served within a certain time (ms) 50% 141 66% 148 75% 155 80% 158 90% 169 95% 184 98% 200 99% 207 100% 207 (longest request) I think the results are impressive: 4947 vs 207 ms = 2289,855% better with the cache activate\nYou have to set correctly your Wordpress cache framework to prevent caching problems; for example, new post not shown on the homepage\u0026hellip; but, I think you should have a caching framework on a wordpress website!\nIf you decide to use it with the NGINX web server, here you are my configuration.\nserver { listen 5.135.145.38:80; server_name blog.mornati.net; root /usr/share/nginx/blog; index index.php index.html index.htm; access_log /var/log/nginx/blog.access.log; error_log /var/log/nginx/blog.error.log; # Use gzip compression # gzip_static on; # Uncomment if you compiled Nginx using --with-http_gzip_static_module gzip on; gzip_disable \"msie6\"; gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_buffers 16 8k; gzip_http_version 1.0; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/png image/gif image/jpeg; # Rewrite minified CSS and JS files location ~* \\.(css|js) { if (!-f $request_filename) { rewrite ^/wp-content/w3tc/min/(.+\\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last; # Use the following line instead for versions of W3TC pre-0.9.2.2 # rewrite ^/wp-content/w3tc/min/([a-f0-9]+)\\/(.+)\\.(include(\\-(footer|body))?(-nb)?)\\.[0-9]+\\.(css|js)$ /wp-content/w3tc/min/index.php?tt=$1\u0026amp;gg=$2\u0026amp;g=$3\u0026amp;t=$7 last; } } # Set a variable to work around the lack of nested conditionals set $cache_uri $request_uri; # POST requests and urls with a query string should always go to PHP if ($request_method = POST) { set $cache_uri 'no cache'; } if ($query_string != \"\") { set $cache_uri 'no cache'; } # Don't cache uris containing the following segments if ($request_uri ~* \"(\\/wp-admin\\/|\\/xmlrpc.php|\\/wp-(app|cron|login|register|mail)\\.php|wp-.*\\.php|index\\.php|wp\\-comments\\-popup\\.php|wp\\-links\\-opml\\.php|wp\\-locations\\.php)\") { set $cache_uri \"no cache\"; } # Don't use the cache for logged in users or recent commenters if ($http_cookie ~* \"comment_author|wordpress_[a-f0-9]+|wp\\-postpass|wordpress_logged_in\") { set $cache_uri 'no cache'; } # Use cached or actual file if they exists, otherwise pass request to WordPress location / { try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?q=$uri\u0026amp;$args; } # Cache static files for as long as possible location ~* \\.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ { try_files $uri =404; expires max; access_log off; } # Deny access to hidden files location ~* /\\.ht { deny all; access_log off; log_not_found off; } # Pass PHP scripts on to PHP-FPM location ~* \\.php$ { try_files $uri /index.php; fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; } } ","date":"12 novembre 2013","externalUrl":null,"permalink":"/speed-up-your-wordpress-site-with-a-cache-system/","section":"Posts","summary":"","title":"Speed up your Wordpress site with a cache system","type":"posts"},{"content":"A thing I found useful in the default configuration of Debian and Ubuntu Systems, is the MOTD message (Message Of The Day) display, any time you login into system, information about packages updates, load, \u0026hellip;\nThe following guide display how you can configure it on a Centos System (or we could say any RedHat based system).\nFirst of all, we need to configure a PAM connection module:\nvi /etc/pam.d/login Adding this line at the end of the file\nsession optional pam_motd.so Then we need to create our scripts and execute it anytime we log into system. For the execution part, as you surely know, any bash execution, run a script named /etc/profile (with, if presents, some user customizations). So we can simply add a call to our scripts at the end of this file, something like /usr/local/bin/dynmotd.\nThen create, and make executable, the scripts, putting all information you want to display to the users. The following is, for example, the script I\u0026rsquo;m using on my home server.\nblog/core/built/scripts/ghost.js: updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); #!/bin/bash PROCCOUNT=`ps -Afl | wc -l` PROCCOUNT=`expr $PROCCOUNT - 5` GROUPZ=`groups` if [[ $GROUPZ == *irc* ]]; then ENDSESSION=`cat /etc/security/limits.conf | grep \"@irc\" | grep maxlogins | awk {'print $4'}` PRIVLAGED=\"IRC Account\" else ENDSESSION=\"Unlimited\" PRIVLAGED=\"Regular User\" fi echo -e \"\\033[1;32m _ _ | | _ (_) _ | | _ ___ ____ ____ ____ ___ ____ ____ ____| |_ _ ____ ____| |_ | || \\ / _ \\| \\ / _ ) | \\ / _ \\ / ___) _ \\ / _ | _)| | | _ \\ / _ ) _) | | | | |_| | | | ( (/ / _| | | | |_| | | | | | ( ( | | |__| |_| | | ( (/ /| |__ |_| |_|\\___/|_|_|_|\\____|_)_|_|_|\\___/|_| |_| |_|\\_||_|\\___)_(_)_| |_|\\____)\\___) \\033[0;35m+++++++++++++++++: \\033[0;37mSystem Data\\033[0;35m :+++++++++++++++++++ + \\033[0;37mHostname \\033[0;35m= \\033[1;32m`hostname` \\033[0;35m+ \\033[0;37mAddress \\033[0;35m= \\033[1;32m`/sbin/ifconfig eth0 | grep \"inet addr\" | awk -F: '{print $2}' | awk '{print $1}'` \\033[0;35m+ \\033[0;37mKernel \\033[0;35m= \\033[1;32m`uname -r` \\033[0;35m+ \\033[0;37mUptime \\033[0;35m= \\033[1;32m`uptime | sed 's/.*up ([^,]*), .*/1/'` \\033[0;35m+ \\033[0;37mCPU \\033[0;35m= \\033[1;32m`cat /proc/cpuinfo | egrep -i '^model name' | head -1 | sed -e 's/^.*: //'` \\033[0;35m+ \\033[0;37mMemory \\033[0;35m= \\033[1;32m`cat /proc/meminfo | grep MemTotal | awk {'print $2'}` kB \\033[0;35m+ \\033[0;37mUpdates \\033[0;35m= \\033[1;32m`cat /tmp/yum_updates.txt` \\033[0;35m++++++++++++++++++: \\033[0;37mUser Data\\033[0;35m :++++++++++++++++++++ + \\033[0;37mUsername \\033[0;35m= \\033[1;32m`whoami` \\033[0;35m+ \\033[0;37mPrivlages \\033[0;35m= \\033[1;32m$PRIVLAGED \\033[0;35m+ \\033[0;37mSessions \\033[0;35m= \\033[1;32m`who | grep $USER | wc -l` of $ENDSESSION MAX \\033[0;35m+ \\033[0;37mProcesses \\033[0;35m= \\033[1;32m$PROCCOUNT of `ulimit -u` MAX \\033[0;35m+++++++++++++++++++++++++++++++++++++++++++++++++++ You can put what you want on this scripts, but if you have commands that takes long time to run, means your login takes long time!! For example, you can see in my script, I\u0026rsquo;m using informations contents into a file named /tmp/yum_updates.txt. This file just has the number of updates available for my system and I\u0026rsquo;m using file because the yum execution could take long time, if a repository updates is needed. The file is updated by another scripts I put on my crontab:\n0 0 * * * /usr/local/bin/check_updates \u0026gt; /tmp/yum_updates.txt The scripts contains:\n#!/bin/sh IFACE=eth0 if [ -n \"$(/sbin/ifconfig $IFACE | /bin/grep RUNNING)\" ]; then /usr/bin/yum -d 0 check-update 2\u0026gt;/dev/null | echo $(($(wc -l)-1)) fi exit 0 Means, if my server is connected (with eth0) then execute the command yum check-update putting the result in the text file.\n","date":"11 novembre 2013","externalUrl":null,"permalink":"/execute-a-dynamic-motd-scripts-on-centos6/","section":"Posts","summary":"","title":"Execute a dynamic MOTD scripts on Centos6","type":"posts"},{"content":"If you work everyday on a Linux shell and need to manage projects on GIT or SVN code control systems, could be useful to get informations about them directly on your bash.\nYou can easily do that with some changes into /etc/bashrc file of your Linux. Go to the end of that file and add the followings lines:\nparse_git_branch() { git branch 2\u0026gt; /dev/null | sed -e '/^[^*]/d' -e 's/* (.*)/(git::1)/' } parse_svn_branch() { parse_svn_url | sed -e 's#^'\"$(parse_svn_repository_root)\"'##g' | awk -F / '{print \"(svn::\"$1 \"/\" $2 \")\"}' } parse_svn_url() { svn info 2\u0026gt;/dev/null | grep -e '^URL*' | sed -e 's#^URL: *(.*)#1#g ' } parse_svn_repository_root() { svn info 2\u0026gt;/dev/null | grep -e '^Repository Root:*' | sed -e 's#^Repository Root: *(.*)#1/#g ' } # vim:ts=4:sw=4 # Colors in Terminal if [ $USER = root ]; then PS1='[33[1;31m][u@h W]$[33[0m] ' else #PS1='[33[01;32m]u@h[33[00m] [33[01;34m]W[33[00m][33[1;32m]$[33[m] ' PS1=\"[33[01;32m]u@h[33[00m] [33[01;34m]W[33[00m][33[1;32m][33[31m]$(parse_git_branch)$(parse_svn_branch)[33[00m][33[1;32m]$[33[m] \" We have added some Bash functions to call git and svn commands and retrieve informations about your code control. Then we override the PS1 variable, used by Bash program to personalize the prompt, adding colors (red for root user) and calling defined functions. The result, when you enter into a repository folder, is the following (for a git project):\nmmornati@desktop raskiidoc(git::master)$ Indicating the repository type (git) and the name of the current branch (master).\n","date":"10 novembre 2013","externalUrl":null,"permalink":"/personalize-your-bash-with-gitsvn-and-colors/","section":"Posts","summary":"","title":"Personalize your bash with GIT/SVN and colors","type":"posts"},{"content":"Backups are importants to prevent file lose: image if tomorrow the disk with all the photos of your family will die. No more photos of your earlier life will be available\u0026hellip; So\u0026hellip; Backups are important. But, it\u0026rsquo;s important to keep your data secret. If you decide to use an online storage, and you need to put private data on it (important documents), it\u0026rsquo;s better if no one can read them!\nFor this reason I\u0026rsquo;d like to propose you a script which backups your data, crypt them using the GPG system and upload then on Dropbox, or Hubic, or anything else you prefer. In this example I show a connection to a remote system to backup files and databases (my WebServer with this blog).\n#!/bin/bash LOGFILE=/tmp/vps_db_backup.log EXPECTED_ARGUMENTS=4 exec 6\u0026gt;\u0026amp;1 # Link file descriptor #6 with the standard output exec \u0026gt; $LOGFILE # stdout sent to $LOGFILE #Check script arguments if [ $# -ne $EXPECTED_ARGUMENTS ] then exec 1\u0026gt;\u0026amp;6 6\u0026gt;\u0026amp;- echo \"No arguments supplied.\" echo \"Script usage:\" echo \" $0 db_username db_password target_folder dest_mail\" exit 1 fi USERNAME=$1 PASSWORD=$2 OUTPUT_FOLDER=$3 DEST_MAIL=$4 FILE_NAME=\"vps_db_backup_$(date +\"%d%m%Y\").sql.gz\" echo \"Starting VPS Backup: $(date +\"%d/%m/%Y %H:%M:%S\")\" echo \"Backup All VPS DBs\" ssh -C user@yourserver.net \"mysqldump --opt --compress --all-databases -u $1 --password='$2' | gzip -9 -c\" \u0026gt; $FILE_NAME echo \"Copy Encrypeted backup files to Hubic\" gpg --passphrase-fd 3 --recipient gpg-email-account --encrypt $FILE_NAME 3\u0026lt;gpgsecret sudo mv $FILE_NAME.gpg /mnt/hubic/default/Backup/VPS/dbs sudo mv $FILE_NAME $OUTPUT_FOLDER echo \"Backup Completed: $(date +\"%d/%m/%Y %H:%M:%S\")!\" mail -s \"VPS Backup Report\" $DEST_MAIL \u0026lt; $LOGFILE exec 1\u0026gt;\u0026amp;6 6\u0026gt;\u0026amp;- # Restore stdout and close file descriptor #6. #rm -f $LOGFILE echo \"Backup Completed!\" exit 0 Some important notes before you can really execute this script.\nYou need a GPG key on you system to crypt the data. If you already have one you can important on the system if it's not already present: gpg --import yourgpgkeyfile You need to share a public key with your server to allow ssh automatic connection. The easy way to this, after the creation of the keys ssh-copy-id user@yourserver.net Create a file containing your gpg password. Here for example named gpgsecret. echo \u0026gt; gpgsecret \u0026lt;\u0026lt; EOF yourgpgpwd EOF chmod 400 gpgsecret Now you should be able to execute the script that will connect to your remote server, execute a backup of all databases, crypt backup using gpg, move it to Hubic folder (that could naturally be the Dropbox folder) and send an email with the log. ./yourscript.sh mysql_user 'mysql_pwd' /destination_folder destination_email@server.net Backup: done. Secured: done. ","date":"4 novembre 2013","externalUrl":null,"permalink":"/put-your-crypted-backups-on-the-cloud/","section":"Posts","summary":"","title":"Put your crypted backups on the cloud","type":"posts"},{"content":"Hubic is the french Dropbox clone, created by OVH, offering 25Gb storage for free. When it sorted out there weren\u0026rsquo;t many clients for the different operating systems but there was an useful (undocumented) fonction: the webdav. Using webdav you could mount your Hubic drive on any system to copy your files. Some weeks ago, when the latest OS client (the linux one) come out, OVH decided to remove the webdav access\u0026hellip; but unfortunately the client at the moment has some bugs and is not so easy-to-use like \u0026ldquo;copy e file into a folder\u0026rdquo;.\nFortunately you can get back your \"local cubic folder\" using Swift and CloudFuse. Here you are the instruction to get it working on Centos6.\nTo simplify the usage of swift, you can use a PHP swift proxy to your hubic account. git clone https://github.com/Toorop/HubicSwiftGateway.git mv HubicSwiftGateway/src/www /var/www/html/hubic mkdir /var/www/html/cache chown apache:apache /var/www/html/cache Here we are supposing your apache root folder is /var/www/html. And naturally, the apache with php5 should already be installed on your system. Now you can install the cloudfuse project. For Centos6 I created the RPM to simplify your work, but if you prefer you can download the sources from github and follow the instructions on the README to build it. The RPMs are located on my repo: http://repo.mornati.net/extras/ You can configure it as yum repository for your centos server using:\necho \u0026gt; /etc/yum.repos.d/mornati-extras.repo \u0026lt;\u0026lt; EOF [mornati-extras] name=MornatiNet-Extras baseurl=http://repo.mornati.net/extras/centos/$releasever/$basearch/ gpgcheck=0 enalbed=1 EOF And install then cloudfuse using yum\nyum -y install cloudfuse To allow a normal user to mount the hubic driver using could fuse, must be in the fuse group, and the mount point should be owned by the fuse group.\nusermod -aG fuse mmornati chgrp fuse /mnt/hubic; chmod g+w /mnt/hubic chgrp fuse /mnt/hubic; chmod g+w /mnt/cubic Before you can mount your hubic drive, you need to configure your account data in a file named .cloudfuse in the home directory of the user you want to use to mount.\nusername=hubicuname api_key=hubicpassword authurl=http://localhost/hubic/ cache_timeout=20 Where, authurl is the url to your Swift HTTP proxy. SO, in my example, the proxy was installed on the same system where I want to mount the Hubic drive too.\nNow you are ready to mount your hubic driver on your system:\n/usr/local/bin/cloudfuse /mnt/hubic/ -o noauto_cache,sync_read If all worked well you should be able to list your Hubic files\nmmornati@desktop ~$ ls /mnt/hubic/default Backup Documents Images Videos ","date":"3 novembre 2013","externalUrl":null,"permalink":"/mount-hubic-cloud-disk-on-your-local-linux/","section":"Posts","summary":"","title":"Mount Hubic cloud disk on your local Linux","type":"posts"},{"content":"I recently purchased a VPS Classic from OVH to migrate my blog. Worked good on my previous host service, but was a shared service that means sometimes the access time to a page was too long (very very long!). A difference on a VPS (Virtual Private Server) comparing to a simple host service, is that you need to manage all the server stuffs: it is an empty box you need to configure to do what want. Considering the base VPS I bought has just 512Mb of RAM I tried to select and tune all the services installed. All the following instructions are for the CentOS Linux distribution.\nInstall the webserver First of all you need to add some external (not base) repositories: rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm\ncat \u0026gt; /etc/yum.repos.d/nginx.repo \u0026lt;\u0026lt; EOF [nginx] name=nginx repo baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=0 enabled=1 EOF Now you are ready to install all required packages:\nyum --enablerepo=remi,remi-php55 install nginx php-fpm php-common php-mysqlnd php-xml php-gd php-pdo mysql-server If all worked well, you environment is ready for your wordpress blog\nConfiguration Nginx (Engine X) web server does not include a module to use php as backend language, for this reason you should have an external \"php server\" to handle this kind of pages, for example php-fpm (PHP FastCGI Process Manager). In this setup we leave the configuration of php-fpm with all the defaults parameters (later we will tune it up...). That means it starts up with a TCP listener (127.0.0.1:9000) and we must configure nginx to send all http requests to it. Create a file in /etc/nginx/conf.d named, for example, blog.conf. The following is my configuration file that is already optimized for the W3C TotalCache Plugin server { listen 5.135.145.38:80; server_name blog.mornati.net; \u0026lt;strong\u0026gt;root\u0026lt;/strong\u0026gt; /path/to/wordpress/file/blog; index index.php index.html index.htm; access_log /var/log/nginx/blog.access.log; error_log /var/log/nginx/blog.error.log; # Use gzip compression # gzip_static on; # Uncomment if you compiled Nginx using --with-http_gzip_static_module gzip on; gzip_disable \u0026quot;msie6\u0026quot;; gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_buffers 16 8k; gzip_http_version 1.0; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/png image/gif image/jpeg; # Rewrite minified CSS and JS files location ~* \\.(css|js) { if (!-f $request_filename) { rewrite ^/wp-content/w3tc/min/(.+\\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last; # Use the following line instead for versions of W3TC pre-0.9.2.2 # rewrite ^/wp-content/w3tc/min/([a-f0-9]+)\\/(.+)\\.(include(\\-(footer|body))?(-nb)?)\\.[0-9]+\\.(css|js)$ /wp-content/w3tc/min/index.php?tt=$1\u0026amp;amp;gg=$2\u0026amp;amp;g=$3\u0026amp;amp;t=$7 last; } } # Set a variable to work around the lack of nested conditionals set $cache_uri $request_uri; # POST requests and urls with a query string should always go to PHP if ($request_method = POST) { set $cache_uri 'no cache'; } if ($query_string != \u0026quot;\u0026quot;) { set $cache_uri 'no cache'; } # Don't cache uris containing the following segments if ($request_uri ~* \u0026quot;(\\/wp-admin\\/|\\/xmlrpc.php|\\/wp-(app|cron|login|register|mail)\\.php|wp-.*\\.php|index\\.php|wp\\-comments\\-popup\\.php|wp\\-links\\-opml\\.php|wp\\-locations\\.php)\u0026quot;) { set $cache_uri \u0026quot;no cache\u0026quot;; } # Don't use the cache for logged in users or recent commenters if ($http_cookie ~* \u0026quot;comment_author|wordpress_[a-f0-9]+|wp\\-postpass|wordpress_logged_in\u0026quot;) { set $cache_uri 'no cache'; } # Use cached or actual file if they exists, otherwise pass request to WordPress location / { try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?q=$uri\u0026amp;amp;$args; } # Cache static files for as long as possible location ~* \\.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ { try_files $uri =404; expires max; access_log off; } # Deny access to hidden files location ~* /.ht { deny all; access_log off; log_not_found off; }\n# Pass PHP scripts on to PHP-FPM location ~* \\.php$ { try_files $uri /index.php; fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; } }\nAs you can see in the latest location configuration, all requests to a php file will redirected to a fastcgi script (the php-fpm). The important things to configure are:\nlisten: address:port where your nginx webserver will listen for requests. Normally the base configuration listen 80 should work. On the OVH VPS you must specify the public ip address of your server to to prevent errors on nginx startup server_name: the variable to configure the virtual host of your webserver. That means all requests coming on your public ip address (the one configured in the listen variable) using the domain name specified in the server_name variable, will be handled by the current configuration. That also means you could have different website on the same nginx server; you just need to assign a different server name. root: the folder on your server containing files you want to serve through the webserver. For example all the wordpress files should be copied inside the folder configured here. You are ready to extract wordpress (or any other php application) to the configured folder. Start all services Now you can test your configuration by starting up all the services: service nginx start service php-fpm start service mysqld start chkconfig nginx on chkconfig php-fpm on chkconfig mysqld on Going to the configured domain name, should allow you the access to your wordpress blog (or to the wordpress setup page if need to configure a new blog).\nTuning Depending on the traffic of your blog, you can try to tune it up to reduce the used resources. For example, my blog has normally 200/300 visit per day from europe and USA, which is important to know the access time to the blog and \"calculate\" the simultaneous connections. An important thing to understand is the meaning of \"simultaneous\": exactly in the same moment two (or more users) access to a website page (producing a request to the webserver, a php page compilation, ...). If you have a user that request a blog post and spends then 5 minutes reading it, in that 5 minutes you could have 2 or 3 other users accessing your blog. User are accessing simultaneously in the real life, but not for your application server: a reading user are not using resources from your server! So\u0026hellip; 200 users a day means you don\u0026rsquo;t have many simultaneous connections (in theory, but is a thing you have to check), so you can configure the webserver (nginx and php-fpm) in consequence.\nnginx tune From nginx side, the important variables to start your tuning are worker_processes and worker_connections: the first one configure how many nginx processes are created on your server (1 by default) and the second one indicates how many connections (clients) can handled by any process. So you can calculate the number of clients allowed on your nginx with: max clients = worker_processes * worker_connections On my server I leaved the default parameters for the nginx server, means 1 process with 1024 connections (file /etc/nginx/nginx.conf)\nWhy if I just say there are few concurrent users? Another important thing to know is how a browser works with a web page. When we ask a page to a webserver (for example a php page), it compile the php file (if needed) and send back to our browser the html version of a file (1 request to the webserver). Then, in the page, we normally have references to JavaScript files, Stylesheets files, images, fonts, \u0026hellip; So the browser, to complete the page we requested, execute other requests to the webserver: one for any static file. So a single user accessing a single page on a webserver, produces 10/20 requests (!!), or more, depending on your page. That means with 2 or 3 users we can easily reach hundreds connection to the server.\nphp-fpm tune If you don\u0026rsquo;t tune the default configuration, the php \u0026ldquo;web server\u0026rdquo; can handle hundreds connections without problems, BUT, it will use \u0026ldquo;lot\u0026rdquo; of ram: more than 200Mb. Is not really an high value, I know, but when you have a virtual machine with 512mb of RAM, means the php-fpm uses half of your RAM. The important configuration part is the one concerning the child processes.\n; Choose how the process manager will control the number of child processes. ; Possible Values: ; static - a fixed number (pm.max_children) of child processes; ; dynamic - the number of child processes are set dynamically based on the ; following directives: ; pm.max_children - the maximum number of children that can ; be alive at the same time. ; pm.start_servers - the number of children created on startup. ; pm.min_spare_servers - the minimum number of children in 'idle' ; state (waiting to process). If the number ; of 'idle' processes is less than this ; number then some children will be created. ; pm.max_spare_servers - the maximum number of children in 'idle' ; state (waiting to process). If the number ; of 'idle' processes is greater than this ; number then some children will be killed. ; Note: This value is mandatory. A child process could handles a single user requests. So, for example, 200 processes means 200 simultaneous users. On my server, after some tests, I\u0026rsquo;m using this configuration:\npm = dynamic pm.max_children = 4 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 2 pm.max_requests = 200 That means I can have maximum 4 concurrent connections to the php part (for security you can increase the max to an higher value): a php thread normally answer to a user in less then a second (!). When php-fpm service start up it creates 2 child processes, the others will be created only if required. You should find the good number for your child processes and put this number into start_servers variable: having required processes ready allow a faster response to the users (no time needed to create a new process). With this configuration I can assure that php-fpm processes took only 70Mb of ram on my server (any new php-fpm process takes about 24Mb of ram more), and when I have moments with \u0026ldquo;many\u0026rdquo; concurrent users could go up to about 120Mb.\n[mmornati@vps38203 ~]$ sudo python ps_mem.py Private + Shared = RAM used\tProgram 4.0 KiB + 27.5 KiB = 31.5 KiB\tdbus-daemon 36.0 KiB + 31.5 KiB = 67.5 KiB\tatd 24.0 KiB + 51.0 KiB = 75.0 KiB\tmingetty (6) 60.0 KiB + 24.0 KiB = 84.0 KiB\tmdadm 224.0 KiB + 62.5 KiB = 286.5 KiB\tcrond 156.0 KiB + 147.0 KiB = 303.0 KiB\tmaster 4.0 KiB + 337.5 KiB = 341.5 KiB\tmysqld_safe 220.0 KiB + 137.0 KiB = 357.0 KiB\tqmgr 332.0 KiB + 35.0 KiB = 367.0 KiB\tinit 484.0 KiB + 81.5 KiB = 565.5 KiB\trsyslogd 728.0 KiB + 173.0 KiB = 901.0 KiB\tnrsysmond (2) 660.0 KiB + 368.5 KiB = 1.0 MiB\tbash 928.0 KiB + 431.5 KiB = 1.3 MiB\tsudo 1.4 MiB + 230.0 KiB = 1.7 MiB\tnginx (2) 1.2 MiB + 481.0 KiB = 1.7 MiB\tpickup 940.0 KiB + 1.8 MiB = 2.7 MiB\tsshd (3) 3.3 MiB + 483.5 KiB = 3.7 MiB\tfail2ban-server 6.4 MiB + 236.5 KiB = 6.6 MiB\tmysqld 7.7 MiB + 175.0 KiB = 7.9 MiB\tnamed 74.4 MiB + 4.7 MiB = 79.1 MiB\tphp-fpm (3) --------------------------------- 109.1 MiB ================================= To create this useful configuration I follow this guide. If !1 then 0 it\u0026rsquo;s an incredible technical blog :)\n","date":"2 novembre 2013","externalUrl":null,"permalink":"/wordpress-nginx-php-fpm-on-ovh-vps/","section":"Posts","summary":"","title":"Wordpress + Nginx + php-fpm on OVH VPS","type":"posts"},{"content":"In the previous post I described how you can tune nginx to keep memory on your server. But, with any tune configuration, you can\u0026rsquo;t chose a value without retest later your server/app performances: have a \u0026ldquo;larger\u0026rdquo; configuration means you are using more resources than the real need; but use a \u0026ldquo;smaller\u0026rdquo; configuration means a poor user experience, like low response, error from the server, \u0026hellip; If you have many facebook friends and you can ask to all of them to get access to your blog at 8:00PM you are executing a really good stress test: many requests from different places, with different ip addresses, \u0026hellip; you have a real life stress test. The problem is that you cannot measure the result. Try to ask to a facebook friend \u0026ldquo;how many milliseconds took the homepage to laod?\u0026rdquo; :D\nI\u0026rsquo;m joking\u0026hellip; but it was just to introduce a tool you can use to make a good stress test with a result measurements. The standard in the opensource for this is Apache JMeter than you can download and install on your machine to register and execute the test. But, in this way, you don\u0026rsquo;t have a really good test because server will always see a single user (same ip address) and can produce response using cached data. On the web you have a free LoadTest Platform, based on JMeter, you can use to make stress test to your wordpress: BlazeMaster. With the simple web interface you can setup your blog url, some pages/posts you want to visit, the number of concurrent users, the rump up period and the test duration. Then you just start your recorded test and you will get an email with the result link when the test will be finished. Here the results I got with the configuration I show you up in my previous post: Memory compared with number of concurrent connections. We can see that memory grow up every time I have an increase of simultaneous connections, which means, for what we saw, php-fpm need another child process to manage that number of connections. If you look in the graph I\u0026rsquo;m testing with users between 20 and 50 (simultaneous users !!), so 4 child processes are enough for 50 concurrent users on a standard wordpress blog. You can check this looking to others graphs produced by BlazeMaster test, such as, response time, errors produced, \u0026hellip; This one is the OK response graph (200) compared to the number of connections. It shows that that server is OK until 60 users and then, with many other users making requests, responses decreases to 0: the server does not have time to respond to all the incoming requests. With 4 max child process in the php-fpm server, we can have at a given time 40 concurrent users, that surely slow down responses, but all of them receive the requested page! That\u0026rsquo;s enough for my little blog! How about yours? :)\n","date":"2 novembre 2013","externalUrl":null,"permalink":"/wordpress-load-tests-to-check-server-performances/","section":"Posts","summary":"","title":"Wordpress: load tests to check server performances","type":"posts"},{"content":"I\u0026rsquo;m actually creating some agents and facts for MCollective running on Windows Server. All agents are developped on WindowsXP machine and then tested and deployed on Windows Server 2003 and Windows Server 2008.\nYou can find all the sources on my github repo: https://github.com/mmornati/mcollective-windows\nOn the README.md file there is a description of facts and agents I created with a little usage doc. Actually there are:\nDNS Fact: add dns information on inventory response Service Agent: to show windows services status and control them EventLog Agent: to get event log messages Enjoy, and if you will use them, let me know if you found any problem/bug. ","date":"27 octobre 2013","externalUrl":null,"permalink":"/mcollective-windows-agents-and-facts/","section":"Posts","summary":"","title":"MCollective: Windows agents and facts","type":"posts"},{"content":"What I\u0026rsquo;m describing is surely not the best method to build a windows package\u0026hellip; but it works and is scriptable (I.e. building it with Jenkins).\nTo proceed, you need to install the following tools on your Windows (build) machine:\nRuby 1.8.7 or later Ruby binaries in your PATH environnement variable (rake, ruby and gem will be used) InnoSetup 5 (http://www.jrsoftware.org/isinfo.php) From my github repo you can download the builder scripts which contains a Rakefile and install_gems.bat. The Rakefile is the script you will then call to execute all the build tasks; install_gems.bat is a batch file packaged into the installer and it will be called in the post installation step to install all gems dependencies.\nConfigure Package Script Before you can start the build script you should check in the downloaded Rakefile if all parameters are corrects for the build environment you are using. # set constant values: LIB_FOLDER = File.expand_path('./lib') INSTALL_FOLDER = File.expand_path('./install') ISCC = \"C:/Programmi/Inno Setup 5/iscc.exe\" ISS_FILE = \"#{INSTALL_FOLDER}/Setup.iss\" APP_TITLE = \u0026ldquo;Marionette Collective\u0026rdquo; EXE_NAME = \u0026ldquo;mcollective\u0026rdquo; EXE_BASENAME = \u0026ldquo;mcollective\u0026rdquo; APP_VERSION = \u0026ldquo;2.3.2\u0026rdquo; In particular you have to check the ISCC variable with the path to your InnoSetup binary file.\nPrepare the installation environment To build the desired version of mcollective you just need to download mcollective tgz sources from http://downloads.puppetlabs.com/mcollective/, extract the downloaded package into preferred location and copy the two previously described file in the sources dir. Rakefile sould be copied into sources root dir (i.e. C:projectsmcollective) install_gems.bat into bin subfolder (i.e. C:projectsmcollectivebin) This one is, for exemple, my mco source dir [caption id=\u0026ldquo;attachment_889\u0026rdquo; align=\u0026ldquo;aligncenter\u0026rdquo; width=\u0026ldquo;150\u0026rdquo;] Mcollective sources root folder[/caption]\n[caption id=\u0026ldquo;attachment_888\u0026rdquo; align=\u0026ldquo;aligncenter\u0026rdquo; width=\u0026ldquo;150\u0026rdquo;] Mcollective Binary Folder[/caption]\nNow you are ready to create the installer package. Open a Windows CMD console, move to mcollective sources folder and execute Rake:\nC:projectsmcollective-2.3.2\u0026gt; rake If the compilation worked well, you should have a sucessful message at the end:\nSuccessful compile (4,547 sec). Resulting Setup program filename is: C:projectsmcollective-2.3.2installmcollective_Setup.exe That means you have your installer file into the install subfolder.\nConvert into MSI Following procedure is surely not the best way to create an MSI windows package. Maybe in the future I'll try to convert the build script using the WIX toolset project which creates directly a real MSI package. Anyway... Using MSI Wrapper you can select the Mcollective EXE installer created with InnoSetup and convert it into a simple MSI.On my github repo, into exe2msi subfolder you can find two pre-configured MSI Wrapper scripts to create a Silent Installation MSI or a Normal MSI. All packages created with this method are available on http://repos.mornati.net/mcollective/\n","date":"26 octobre 2013","externalUrl":null,"permalink":"/how-to-build-mcollective-windows-package/","section":"Posts","summary":"","title":"How to build MCollective Windows package","type":"posts"},{"content":"After the problem I had on my Nexus 7 and the wipe data test, my tables was always locked on the boot image. So, the second thing (and I think the last one too) I could test, was a complete reinstallation of the rom. Before going ahead, you need a computer with the Android SDK installed and the platform-tool folder in the class path of your machine (means you can run, for example, fast boot from everywhere).\nDownload the factory image for your nexus device from here: https://developers.google.com/android/nexus/images I suppose the same thing should work for any other device if you can retrieve the factory ROM. Reboot your device into fastboot menu (as I described here) Enter in the ROM folder you downloaded, where you should have the script to reinstall it (flash-all.sh or flash-all.bat) Execute the script and you should see on the screen a question asking you if you want to unlock the boot loader (you must unlock it to install the stock ROM) ![bootloader_n_7_03_grand](/images/nexus-7-reinstall-stock-rom-and-re-lock-boot-loader/00-bootloader_n_7_03_grand_ytf4yi.png) On your computer screen you can follow the ROM installation log. When procedure ends (if no problem occurred) your device will be automatically boot. For security reasons, as suggested by Google too, it's better to relock your boot loader (in any case you can always unlock it if needed). Reboot the device in fast boot mode From your computer execute fastboot oem lock MacBook-Pro-di-Marco:nakasi-jwr66y mmornati$ fastboot oem lock \u0026lt; waiting for device \u0026gt; ... (bootloader) Bootloader is locked now. OKAY [ 1.447s] finished. total time: 1.447s Now you have a completely new device (at least software side). ","date":"25 octobre 2013","externalUrl":null,"permalink":"/nexus-7-reinstall-stock-rom-and-re-lock-boot-loader/","section":"Posts","summary":"","title":"Nexus 7: reinstall stock rom and re-lock boot loader","type":"posts"},{"content":"I recently get back working on MCollective for windows for one of our customers and, after searches on google, I discovered that Puppet Labs apparently decided to release the MCollective package for windows only with Puppet Enterprise. Really strange things\u0026hellip;\nAnyway, MCollective is an opensource project, I need a package for windows to script installation in an easy way: I create the installation package. I love opensource :D In the past I\u0026rsquo;ve already created package for the 2.0.0 version, so I just retrieved the old script and repackage latest sources.\nYou can find windows packages here: http://repos.mornati.net/mcollective/\nYou can install mcollective using the graphical wizard, but if you want to script it using, for example, puppet you need to skip the wizard and proceed with a silent installation.\nmcollective_2_3_2_Setup.exe /VERYSILENT /LOG=\"mco_install.log\" /DIR=\"C:\\mcollective\" On the InnoSetup (the tool I used to package Mcollective) you can find a list of all available cli parameters: http://www.jrsoftware.org/ishelp/index.php?topic=setupcmdline Naturally to get it working, your server should have a Ruby version installed and have the binary folder into the PATH environment variable.\nAfter you configured the server.cfg file with AMQP server and security information, you can startup the service. You can find it in the Windows Services tool and the service name is \u0026ldquo;The Marionette Collective\u0026rdquo;.\nYour windows machine is now in you mcollective network!\n","date":"24 octobre 2013","externalUrl":null,"permalink":"/mcollective-232-windows-installer/","section":"Posts","summary":"","title":"Mcollective 2.3.2 Windows Installer","type":"posts"},{"content":"I like to use my smartphone with all functions always activated, but this is naturally a thing which contributes negatively to battery life. I tried to switch off, for example, the Bluetooth when I\u0026rsquo;m not using it, but then I get back to my car and only when I\u0026rsquo;m receiving a phone call I discover I\u0026rsquo;m not linked with the car because I dismembered to reactivate the Bluetooth function\u0026hellip; So, no\u0026hellip; I cannot use a device in this way. Recently I discover a little (but powerful) application which helps me doing this automatically: NFC Task Launcher. Yes, the name means you should use it with NFC tags, but developers also add other interesting features even without NFC: wifi connect/disconnect, bluetooth connect/disconnect, gps position, \u0026hellip; To show you how my smartphone usage has changed, I just describe some tasks I added to this application.\nEvent: Connection to Home WiFi. Actions: disable GPS, disable 3G data, disable bluetooth Event: Disconnecting from Home Wifi. Actions: Enable GPS, Enable 3G Data, Enable bluetooth Event: Connection to Car Bluetooth system (done automatically after wifi disconnection event) Actions: Disable WiFi, Switch to Driving Mode Event: Disconnection from Bluetooth car system Action: Enable WiFi, Switch off Driving mode And some other events when I'm at office, when will be night, when I'm in a specific GPS position (i.e. to the gym switch to vibrate mode). The application allow many other actions like send a message, start a phone call, make a chekin on facebook, start an application, set an alarm, ... You can do what you want, you just need imagination!! :) And then, of course, if you have NFC tags (actually are not too expensive) you can improve the usage of the application. For example a task \"when is 22H set lock phone mode and add an alarm for 7AM\" is not bad, but, if you are outside for a party the task is still fired. If you put, for example, an nfc sticker near your bed, you can say this task will be execute when you put the phone on the sticker (Near field communication)! :) With all this stuffs executed automatically, I don't need to take care about switch on and off functions but I can say that I don't need to charge my battery any day like before![gallery link=\"file\" ids=\"853,854,855\"] https://www.youtube.com/watch?v=17ASsGo8kIk\nUPDATE: After the post comment (you can read) about a different app to do something similar, I investigated a little bit and I found a better application (with more triggers and events available), in my opinion, easy to use and with a cool web interface: https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid\n","date":"12 septembre 2013","externalUrl":null,"permalink":"/improve-your-android-battery-life/","section":"Posts","summary":"","title":"Improve your Android battery life","type":"posts"},{"content":"In the IT work we always need to keep and share information, but right now, any thing we tested for this was abandoned after some week of usage. The problem is normally we have to work on the technical stuffs, then write the customer documentation and, normally, during these processes we have to write important information on the internal wiki (how to connect to customer servers, common problems, \u0026hellip;).\nFor this reason a colleague of mine creates a \u0026ldquo;ruby builder\u0026rdquo; (rake) to compile an asciidoc document into a PDF document, html page or slidy page: raskiidoc. In this way we can easily create a single document and then use it as customer documentation, information for internal wiki and, if needed, slides for a training/presentation. Following the \u0026ldquo;write once use many\u0026rdquo; we should reach our goal! (at least I hope so ;)).\nAfter this the problem was: how we can publish all html pages (with a little bit of security) without having to \u0026ldquo;cut \u0026amp; paste\u0026rdquo; into a wiki or any other manual operation. That the reason for the FiKi (File Based Wiki). The usage is really simple, you just need to configure the security (actually we have created ldap and file authentication) and the data directory (where you want to store the html files) and then FiKi will display all available contents. In the data folder you should create a folder for any arguments you want to add to the wiki, and inside the argument folder you can push all html/pdf files created using raskiidoc. Easy, isn\u0026rsquo;t it? :)\nTo complete the CID (Continuos Integration Documentation) we created jobs in jenkins to automatically download the latest doc version from our git repository, build it and push the result into the fiki data folder! We just need to modify the asciidoc, push it and customer doc (pdf) and wiki pages are automatically updated. SO we are sure to read always the latest information on the wiki!\nWe realized two little videos to shown the base functions of FiKi\nhttp://youtu.be/o_-KKtCQss0\nand how you can create and customize new arguments\nhttp://youtu.be/xWY3H4A7P0g\n","date":"9 septembre 2013","externalUrl":null,"permalink":"/fiki-the-file-based-wiki/","section":"Posts","summary":"","title":"Fiki: the file based Wiki","type":"posts"},{"content":"The summer holidays are coming and with them the fear of being robbed. Even if I\u0026rsquo;ve an home alarm I\u0026rsquo;d like to be sure than nothing are happening in the house (watching films we learnt that is really easy to deactivate an house alarm :P), so I found another life (at least during holidays) for my RaspberryPi (but it could be any other computer with an USB and a linux operating system installed or booted with a live distribution). I can tell you that is really useful in order to control your children :D\nNoise Detection # To detect the noise in the house (on a linux OS) a good solution is to use some linux command that allow you to record \u0026ldquo;sound\u0026rsquo; and then analyze the track: arecord and sox. I created a little Ruby script (that I found some months ago on the net, but I completed and improved it later) that record for n seconds using the computer/webcam microphone and send you an email if the recorded noise threshold is greater than the set value.\nYou can check the script usage guide with the -h parameter\n./noise_detection.rb -h Usage: noise_detection.rb -m ID [options] -m, --microphone SOUND_CARD_ID REQUIRED: Set microphone id -s, --sample SECONDS Sample duration -n, --threshold NOISE_THRESHOLD Set Activation noise Threshold. EX. 0.1 -e, --email DEST_EMAIL Alert destination email -v, --[no-]verbose Run verbosely -d, --detect Detect your sound cards -t, --test SOUND_CARD_ID Test soundcard with the given id If you execute the script with the -d parameter it will list you the list of available sound cards that you can use to test or to run the script (you just need to get the sound card id)\n./noise_detection.rb -d Detecting your soundcard... 0 [ALSA ]: BRCM bcm2835 ALSbcm2835 ALSA - bcm2835 ALSA bcm2835 ALSA 1 [U0x46d0x8d7 ]: USB-Audio - USB Device 0x46d:0x8d7 USB Device 0x46d:0x8d7 at usb-bcm2708_usb-1.2, full speed For example, on my RaspberryPi I\u0026rsquo;m using the webcam Microphone (USB device), so I need to start this script with the id 1. You can even test your device to check if all works well using the -t parameter.\n./noise_detection.rb -t 1 -v Testing soundcard... Samples read: 40000 Length (seconds): 5.000000 Scaled by: 2147483647.0 Maximum amplitude: 0.224640 Minimum amplitude: -0.993805 Midline amplitude: -0.384583 Mean norm: 0.002984 Mean amplitude: -0.002957 RMS amplitude: 0.006520 Maximum delta: 0.991394 Minimum delta: 0.000000 Mean delta: 0.000688 RMS delta: 0.006875 Rough frequency: 1342 Volume adjustment: 1.006 The test is also important to detect your \u0026ldquo;standard\u0026rdquo; noise threshold. If you don\u0026rsquo;t want to receive an email any minute it\u0026rsquo;s better to set a good threshold for your environment (for example, the value will be different if you live in an apartment in the center of Paris or in a wooden house in the Death Valley). In my test the Maximun amplitude is 0.22464.\nNow we know the soundcard id and the threshold of our environment, so we can start the noise script. At the moment (if you check on github) I\u0026rsquo;m working on the init.d file to start the script as a service. But, even without it you can execute the script in background using, for example, the nohup command.\n./noise_detection.rb -m 1 -n 0.30 -e test@mail.com -v ./noise_detection.rb:94: warning: already initialized constant THRESHOLD Script parameters configurations: SoundCard ID: 1 Sample Duration: 5 Output Format: S16_LE Noise Threshold: 0.3 Record filename (overwritten): /tmp/noise.wav Destination email: test@mail.com 0.228607 no sound 0.227264 no sound If sound is detected the script will send you an email (on the provided email address) with the wav file in attachment (so you can check what kind of noise there is in your house). The actual version of the script uses the localhost sendmail service, so you need to install it on your raspeberrypi\nMotion Detection # The motion dection is really simple using the motion project. You just need to install and configure it. For the installation on the raspberrypi you can, for example, follow this guide. If you want to receive an email when motion is detected you need to add a configuration line in motion.conf file. For example, something like:\non_event_start echo \"Movement has been detected on: %d %m %Y. The time of the movement was: %H:%M (Hour:Minute). The Pictures have been uploaded to your FTP account.\" | mail -s \"Home: Motion Detected!\" test@mail.com On_event_start means when motion is detected.\nWith these two services started on your raspberry pi you will be notified for any noise or movement in your house\u0026hellip; or if your child is crying ;) Next days I\u0026rsquo;ll complete the noise detector init.d script and I\u0026rsquo;m also working on web interface to control the activation of the two services.\n","date":"5 juillet 2013","externalUrl":null,"permalink":"/raspberrypi-motion-and-noise-detection/","section":"Posts","summary":"","title":"RaspberryPI: Motion and Noise detection","type":"posts"},{"content":"If you are a backup maniac like me, you like to have multiple ways (and on different locations) to backup all your data. Even if on bluehost I\u0026rsquo;ve some other services making backups of my data, I\u0026rsquo;ve also created a script executed by my home computer every night to extract mysql databases to a local NAS.\n#!/bin/bash LOGFILE=/tmp/bluehost_backup.log EXPECTED_ARGUMENTS=4 exec 6\u0026gt;\u0026amp;1 # Link file descriptor #6 with the standard output exec \u0026gt; $LOGFILE # stdout sent to $LOGFILE #Check script arguments if [ $# -ne $EXPECTED_ARGUMENTS ] then echo \"No arguments supplied.\" echo \"Script usage:\" echo \" $0 bluehost_db_username bluehost_db_password target_folder dest_mail\" exit 1 fi USERNAME=$1 PASSWORD=$2 OUTPUT_FOLDER=$3 DEST_MAIL=$4 FILE_NAME=\"bluehost_db_backup_$(date +\"%d%m%Y\").sql.gz\" echo \"Starting Bluehost Backup: $(date +\"%d/%m/%Y %H:%M:%S\")\" echo \"Backup All Bluehost DBs\" ssh -C user@mornati.net \"mysqldump --opt --compress --all-databases -u $1 --password='$2' | gzip -9 -c\" \u0026gt; $FILE_NAME sudo mv ./$FILE_NAME $3 echo \"Backup Completed: $(date +\"%d/%m/%Y %H:%M:%S\")!\" mail -s \"Bluehost Backup Report\" $DEST_MAIL \u0026lt; $LOGFILE exec 1\u0026gt;\u0026amp;6 6\u0026gt;\u0026amp;- # Restore stdout and close file descriptor #6. rm -f $LOGFILE echo \"Backup Completed!\" exit 0 The script make an ssh connection to your Bluehost account, execute the mysqldump and directly download the compressed result (no space is required on your Bluehost account to execute the backup). Then it will send to a provided account, an email with the script log information.\nIt\u0026rsquo;s important to say that you naturally need an ssh connection to your Bluehost account, and you also should copy your home pc ssh public key to Bluehost account to allow key authentication (no password required for connection).\nTo execute the script you should run something like:\n./backup_bluehost_db.sh mornatin 'yourpassword' /mnt/nasbackup/bluehost youremail@mornati.net Adding this execution line to your crontab, provides you a complete bluehost backup service!\nIf you have any problem displaying the script in your browser, the source is available on GitHub.\n","date":"30 juin 2013","externalUrl":null,"permalink":"/script-how-to-backup-bluehost-databases-on-your-pcs/","section":"Posts","summary":"","title":"Script: How to backup Bluehost databases on your PCs","type":"posts"},{"content":"Using a Maven to build your Java project, you can easily create a static Class containing Version and Release of your project; for example, you can then access to this class to show the version, for example, on your main project page. The important thing is that you don\u0026rsquo;t need to maintain this class nor to commit it: any build will automatically regenerate, and then build, the class file.\nHere the code to put in your maven pom.xml in the \u0026lt;build\u0026gt; tag:\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-antrun-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;run\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;phase\u0026gt;generate-sources\u0026lt;/phase\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;tasks\u0026gt; \u0026lt;property name=\"src.dir\" value=\"${project.build.sourceDirectory}\" /\u0026gt; \u0026lt;property name=\"package.dir\" value=\"net/mornati/configuration\" /\u0026gt; \u0026lt;property name=\"package.name\" value=\"net.mornati.configuration\" /\u0026gt; \u0026lt;property name=\"buildtime\" value=\"${maven.build.timestamp}\" /\u0026gt; \u0026lt;echo file=\"${src.dir}/${package.dir}/Version.java\" message=\"package ${package.name};${line.separator}\" /\u0026gt; \u0026lt;echo file=\"${src.dir}/${package.dir}/Version.java\" append=\"true\" message=\"public final class Version {${line.separator}\" /\u0026gt; \u0026lt;echo file=\"${src.dir}/${package.dir}/Version.java\" append=\"true\" message=\" public static String VERSION=\"${project.version}-${buildtime}\";${line.separator}\" /\u0026gt; \u0026lt;echo file=\"${src.dir}/${package.dir}/Version.java\" append=\"true\" message=\"}${line.separator}\" /\u0026gt; \u0026lt;echo message=\"BUILD ${buildtime}\" /\u0026gt; \u0026lt;/tasks\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; If necessary, with a maven property you can control the timestamp format used to inject the \u0026ldquo;release\u0026rdquo; in your version file:\n\u0026lt;properties\u0026gt; \u0026lt;maven.build.timestamp.format\u0026gt;yyyyMMddHHmmss\u0026lt;/maven.build.timestamp.format\u0026gt; \u0026lt;/properties\u0026gt; The result is a Version.java file containing a public method named VERSION like this:\npublic static final String VERSION = \"2.0.1-20130627220534567\" that are the project version specified in your project pom and the build timestamp with the provided format.\nAnd then you can simply access to this property with a jsp/java/\u0026hellip; file:\n\u0026lt;tr\u0026gt; \u0026lt;td class=\"exp-footer\"\u0026gt; Mornati.net Project Version: \u0026lt;b\u0026gt;\u0026lt;%= net.mornati.configuration.Version.VERSION %\u0026gt;\u0026lt;/b\u0026gt; \u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; A thing to know is that if you want to use the Version class inside another java class, your development IDE shows you an error before the first build (the file is not present), but normally build should even work without problem and, once your file is created, the error will not be shown anymore.\nA good idea could be to add this file to ignore of your source repository. For git for example, put in your .gitignore file:\n.svn .idea target *.iml *.iws net/mornati/configuration/Version.java ","date":"20 juin 2013","externalUrl":null,"permalink":"/maven-automatically-create-a-version-class/","section":"Posts","summary":"","title":"Maven: automatically create a version Class","type":"posts"},{"content":"For an high dynamic application I need to allow users (admin users) to update some translation messages without having to redeploy application any time (for example, some messages about operation to accomplished change almost any week). To allow this with the framework I\u0026rsquo;m using (Spring MVC) I decided to change the Message Resource politics, adding a database driven in priority to the \u0026ldquo;standard\u0026rdquo; properties file messages.\nIn your application context (i.e. root-context.xml) you have to configure the two message resource beans:\n\u0026lt;bean id=\u0026#34;propertiesMessageSource\u0026#34; class=\u0026#34;org.springframework.context.support.ReloadableResourceBundleMessageSource\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;basename\u0026#34; value=\u0026#34;/WEB-INF/messages/messages\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;defaultEncoding\u0026#34; value=\u0026#34;UTF-8\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;cacheSeconds\u0026#34; value=\u0026#34;0\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;fallbackToSystemLocale\u0026#34; value=\u0026#34;false\u0026#34;/\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;messageSource\u0026#34; class=\u0026#34;net.mornati.DatabaseDrivenMessageSource\u0026#34;\u0026gt; \u0026lt;constructor-arg ref=\u0026#34;messageResourceService\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;parentMessageSource\u0026#34; ref=\u0026#34;propertiesMessageSource\u0026#34;/\u0026gt; \u0026lt;/bean\u0026gt; The propertiesMessageSource is the one using the properties file with translated message, the messageSource (the one used by default for the Spring MVC framework) just inject the service to load messages from database and set the propertiesMessageSource has a parent (the fallback message source).\npackage net.mornati.configuration; import net.mornati.model.MessageResource; import net.mornati.service.MessageResourceService; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.support.AbstractMessageSource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; public class DatabaseDrivenMessageSource extends AbstractMessageSource implements ResourceLoaderAware { private Logger log = Logger.getLogger(getClass()); private ResourceLoader resourceLoader; private final Map","date":"13 juin 2013","externalUrl":null,"permalink":"/spring-mvc-database-messagesource-fall-back-to-properties-file/","section":"Posts","summary":"","title":"Spring MVC: Database MessageSource fall back to properties file","type":"posts"},{"content":"Even if put an SSD drive in your mac, will quicken the very most of the daily activities (ex. startup from 1 minute to 12 seconds), remains a serious problem with this type of technology: the number of disk writes is limited.\nIn an everyday usage maybe you don\u0026rsquo;t install/delete applications or your store documents in a separate device (usb stick for example), but your OS will surely write stuffs to your SSD disk: logs, tmp files, downloads (with default location), \u0026hellip; There are some tips/changes you can make on your OSX, to try to extend the life of your powerful SSD drive.\nSSD TRIM\n\u0026ldquo;TRIM is a feature that allows solid state drives to automatically handle garbage collection, cleaning up unused blocks of data and preparing them for rewriting.\u0026rdquo; and, knowing on an SSD drive you don\u0026rsquo;t have access time (there\u0026rsquo;s no mechanical heads to place before read data), with the TRIM function an SSD will write any time on a different area of your disk. In this way you can extend your disk life.\nUnfortunately the SSD TRIM function is activated by default for all MacBook with an \u0026ldquo;Apple\u0026rdquo; SSD, but if you decide to change your disk later, there is nothing prepare in OSX to activate this function. You can simply bypass this limitation using Chameleon SSD Optimizer: open the application, click on the trim button, restart your mac and that\u0026rsquo;s all.\nMEDIA FILES First easy settings is to put all your media files into a different disk: iTunes, iPhoto, iMovie, Aperture, \u0026hellip; libraries could be placed in a secondary disk (for a disk size reason too).\nHIBERNATE\nBy default any MacBook is not configured with a \u0026ldquo;real\u0026rdquo; hibernate mode: the RAM content is not directly written to the disk any time you close the lid. It uses a Safe Sleep: the content of the RAM remains in place and the RAM is powered to keep these data. When the Mac pass the standby delay, this content is written to the disk to enter the hibernate mode.\nAlways using Chameleon you can easily change the hibernate mode forcing, for example, your Mac to never write to the SSD. Problem is that your battery runs flat you will loose your session (and maybe documents/informations).\nAlternative to this method could be just to place the \u0026ldquo;ram file\u0026rdquo; to a different driver (if you have the second disk drive in your mac).\nUsing the command pmset -g you can check the current configuration for hibernate.\nMacBook-Pro-di-Marco:~ mmornati$ sudo pmset -g Active Profiles: Battery Power -1* AC Power -1 Currently in use: standbydelay 4200 standby 0 halfdim 1 sms 1 hibernatefile /var/vm/sleepimage disksleep 10 sleep 10 hibernatemode 3 ttyskeepawake 1 displaysleep 2 acwake 0 lidwake 1 The important informations here are: standbydelay and hibernatefile. The first one say us that our mac will wait 4200 seconds before entering the \u0026ldquo;real\u0026rsquo; hibernate mode (before any information is written to the disk). The hibernatefile is the location where ram content are stored. For example I decided just to relocate my sleepimage file.\nCreate folder on the second driver:\nMacBook-Pro-di-Marco:~ mmornati$ mkdir -p /Volumes/Media/System/vm Change the hibernatefile property\nsudo pmset -a hibernatefile /Volumes/Media/System/vm/sleepimage Check your currents settings\nMacBook-Pro-di-Marco:~ mmornati$ sudo pmset -g Active Profiles: Battery Power -1* AC Power -1 Currently in use: standbydelay 4200 standby 0 halfdim 1 sms 1 hibernatefile /Volumes/Media/System/vm/sleepimage disksleep 10 sleep 10 hibernatemode 3 ttyskeepawake 1 displaysleep 2 acwake 0 lidwake 1 Now any time your mac enters hibernate mode, the ram content is written in /Volumes/Media drive (my second internal hd).\nDownload Location\nIn your(s) browser(s), any time you want to download files, they are written into default download folder (~/Downloads). If you use a single driver in your mac, you can change settings in it to select a different location. If you want to \u0026ldquo;keep\u0026rdquo; this default location but write file into the secondary disk, you can create a symbolic link to it.\ncp -r ~/Downloads /Volumes/Media/ sudo rm -rf ~/Downloads/ ln -s /Volumes/Media/Downloads/ ~/Downloads Now, when any of your browsers, will write to ~/Downloads is written to the secondary disk into the configured location.\nThere are surely many other services you can switch to the secondary disk drive, but proceeding this way you have an SSD drive you are not using for you Mac (all file are written, and so read, from the secondary disk).\nRessources\nhttp://www.garron.me/en/mac/macbook-hibernate-sleep-deep-standby.html\n","date":"3 juin 2013","externalUrl":null,"permalink":"/macbook-pro-and-ssd-disk-tips/","section":"Posts","summary":"","title":"MacBook Pro and SSD Disk Tips","type":"posts"},{"content":"After the latests Fedora updates I\u0026rsquo;m getting a boring problem with the unlock: I can normally login after the startup but not after a lock screen (CTRL + ALT + L for example).\nLooking into log you should find something like the following:\nMar 25 13:33:15 notebook gdm-password][12579]: AccountsService-WARNING: Failed to connect to the ConsoleKit seat object: No space left on device It\u0026rsquo;s a gnome-shell bug, as described here: https://bugzilla.redhat.com/show_bug.cgi?id=872118 But in this way my desktop is completely useless.\nI found two differents way to workaround/fix the problem.\nConnect with root on a new terminal (CTRL + ALT + F2), and here kill the gnome-shell process. mmornati@notebook ~$ sudo ps aux | grep gnome-shell mmornati 1967 6.4 3.9 2041492 156052 ? Sl 21:15 1:25 /usr/bin/gnome-shell mmornati 2127 0.0 0.3 739228 14552 ? Sl 21:15 0:00 /usr/libexec/gnome-shell-calendar-server gdm 3531 0.5 2.0 1425268 78348 ? Sl 21:28 0:02 gnome-shell --mode=gdm mmornati 4570 0.0 0.0 109184 884 pts/1 S+ 21:37 0:00 grep --color=auto gnome-shell mmornati@notebook ~$ sudo kill -9 3531 A problem with this solution is that we have to execute a manual task any time we want to unlock Fedora screen A second method consists in changing a parameter of the inotify process to allow more watches users. I\u0026rsquo;m not sure, at the moment, if this could cause some others problems, but for me it works now. To this you need to create a new sysctl configuration file for inotify, for example inotify.conf with this parameter inside\nmmornati@notebook ~$ cat /etc/sysctl.d/inotify.conf fs.inotify.max_user_watches=100000 A1l should work correctly now!\nEnjoy ","date":"24 mars 2013","externalUrl":null,"permalink":"/fedora-18-cant-unlock-the-screen/","section":"Posts","summary":"","title":"Fedora 18: Can't Unlock the Screen","type":"posts"},{"content":"You can use a RaspberryPI computer in many differents way. Personally I decided to use it as mediacenter with an XBMC program.\nAfter the first installation I spent lot of time to configure any things in XBMC: share folders for videos, share folders for music, \u0026hellip; So the problem I had was about the \u0026ldquo;update\u0026rdquo; process. The OpenELEC is based on a linux distribution but there is nothing to automate packages installation or update (I mean something like yum or apt-get), I it\u0026rsquo;s not possible for any system update to reconfigure anything.\nI looked on internet and in the end I found a simple script that help you in the update process.\n#!/bin/bash # change working directory cd /storage # location of the nightlies url=\"http://openelec.thestateofme.com/\" # get base, revision and filename of last build last_base=`curl -s $url | grep .tar.bz2 | sed 's/.*\\(OpenELEC-RPi.*\\).tar.bz2.*/\\1/' | sort | tail -1` last_revision=`echo $last_base | sed 's/.*\\(r[0-9]*\\)/\\1/'` last_filename=$last_base.tar.bz2 # folder name is set equal to base foldername=$last_base # get currently installed revision this_revision=`cat /etc/version | sed 's/.*\\(r[0-9]*\\)/\\1/'` # check if currently installed revision is up-to-date if [ $this_revision == $last_revision ] then echo \"System is up-to-date, no update required.\" exit else echo \"Update required, will download latest version.\" fi # clean up previously interrupted update if [ -a $last_filename ]; then echo \"Clean up previously interrupted update files.\" rm $last_filename fi if [ -a $foldername ]; then echo \"Clean up previously interrupted update files.\" rm -rf $foldername fi # download corresponding file to working directory urltolast=$url/$last_filename wget $urltolast echo -e \"Download complete\\n\" # uncompressing the tarball echo \"Uncompressing tarball, files extracted:\" tar -xvjf $last_filename # check if image folder exists, otherwise exit if [ ! -d $foldername ]; then echo \"Cannot find extracted folder.\" exit fi # check if .update folder exists, otherwise create it if [ ! -d /storage/.update ]; then mkdir /storage/.update fi # move OpenELEC files (including .md5 files) to update folder mv $foldername/target/* /storage/.update/ echo -e \"\\nOpenELEC files succesfully moved to update directory\" # clean up rm -r $foldername rm $last_filename echo \"Temporary files deleted\" # sync and reboot system to apply updates echo \"System will restart shortly\" echo \"Enjoy!\" sleep 5s sync reboot Sorry to the author because I can\u0026rsquo;t remember where I found it, and I don\u0026rsquo;t even know if it\u0026rsquo;s the original version or if I modified it. In any case it works perfectly.\nWhat you just need to do is to copy it on your RaspberryPi/OpenELEC system and execute it. The script will check if a new version is available, it will download it and then reboot the raspberry when all is ready for the update (update will be automatically installed after the reboot).\nHow you can copy the script on your OpenELEC and execute an update? You can make an SSH connection to the RaspberryPi.\nssh root@192.168.0.25 ############################################## # OpenELEC - The living room PC for everyone # # ...... visit http://www.openelec.tv ...... # ############################################## OpenELEC Version: devel-20130119143821-r12975 OpenELEC git: 6bc259fb5cdf4f941e85e43132a0a31e211af937 root@192.168.0.25's password: Where 192.168.0.25 is the ip address of my Raspberry. I make the SSH connection using a Linux/Mac computer (where ssh is available by default on the command line); if you are on Windows, you need to use Putty to make the ssh connection!\nThe default username/password to connect to OpenELEC via SSH are:\nusername: root password: openelec\nAfter connection you can create your script where you want on your system (you are root so be carefull because you can do anything). Normally where you have a lot of free space is /storege, but if you are not sure, you can check it with a df -h:\ndf -h Filesystem Size Used Available Use% Mounted on none 185.0M 90.1M 94.9M 49% /dev /dev/mmcblk0p1 124.7M 98.8M 26.0M 79% /flash /dev/mmcblk0p2 3.6G 127.5M 3.3G 4% /storage /dev/loop0 90.0M 90.0M 0 100% / none 186.5M 0 186.5M 0% /dev/shm Then you can create the script file using vi, for example typing something like:\nvi update.sh And here you can paste the content of the script I put in this article. After this you need to make the script executable and execute it, with:\nchmod +x update.sh ./update.sh You should have an output like the following:\n./update.sh Update required, will download latest version. Connecting to openelec.thestateofme.com (46.149.19.9:80) OpenELEC-RPi.arm-dev 100% |*****************************************************************************************************| 92078k 0:00:00 ETA Download complete Uncompressing tarball, files extracted: OpenELEC-RPi.arm-devel-20130228144321-r13387/ [...] OpenELEC-RPi.arm-devel-20130228144321-r13387/target/SYSTEM OpenELEC-RPi.arm-devel-20130228144321-r13387/openelec.ico OpenELEC-RPi.arm-devel-20130228144321-r13387/INSTALL OpenELEC files succesfully moved to update directory Temporary files deleted System will restart shortly Enjoy! Finished! After the reboot your OpenELEC is updated. You can check it simply with an ssh connection (after the connection you should see the version directly on your screen) or going in the settings in your XBMC.\n","date":"27 février 2013","externalUrl":null,"permalink":"/update-openelec-on-raspberrypi/","section":"Posts","summary":"","title":"Update OpenELEC on RaspberryPI","type":"posts"},{"content":"A useful thing you can do with an Android phone, that is actually impossible even to imagine on the iPhone, is to control it from your computer. Two tools I tested to do this are: AIrDroid and Kies Air.\n![2013-01-16 21.37.59](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/01-2013-01-16-21_37_59_dvrtyh.png)\n\u0026nbsp; Both two application follow the same usage procedure: install an application on your android phone, start it up (when you are connected to a Wireles network), connect to your phone address (shown on the application home page) using a browser\u0026hellip; and enjoy your phone.\nAt the beginning I tested Kies Air, knowing it is a Samsung product and I\u0026rsquo;ve a Galaxy Phone, I thought was surely the best choice because was created by the phone manufacturer.\nKIES AIR The interface is pretty interesting. After the connection,with your browser you have an home page with all important things on your phone: latest call, latest message, music, ... ![kies-main-interface](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/02-kies-main-interface_abibvt.png)\nInteresting because you have all things \"in a click\"; but to use all web application functions, you should install the Java Virtual Machine on your computer and linked it with your browser.\n![kies-java](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/03-kies-java_hhrr2o.png)\nYou can use the web application to send or retrieve files from your device, but you need java if you want to transfer more than one file at the same time. So it is impossible to have all functions on, for example, tabled browser.\n![kies-upload](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/04-kies-upload_d80n4b.png)\nOn phone side, the interface is really simple because is just a server that allows you to connect to your phone. You start it up and when a client try to connect to your device a popup propose you a pin code you must insert to start the connection between the two devices.\n![2013-01-16 21.33.38](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/05-2013-01-16-21_33_38_qwgari.png)\nI was pretty satify by this application that allows me to control my phone all day long, without directly using it! But I decide to test AirDroid because I read many positive comments on the Google Play.\nAIRDROID As I said at the beginning is pretty the same thing. The main page of the application shown just a dashboard with links to access to the differents areas: messages, photo, video, ... ![airdroid-message](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/06-airdroid-message1_e4jnj1.png)\nAn important difference, I like, is that the application does not use Java so, for example, even the multi file upload works with any browser; you can connect to your phone via AirDroid using your iPad browser, and have access to all functions!\n![airdrop-upload](/images/airdroid-vs-kies-air-control-your-android-with-your-browser/07-airdrop-upload_on89it.png)\nAnother thing I like is the ability to open a link on your phone simply pasting it in a text area on the desktop app. And you have then, there are some others functions requiring a rooted device. An example is screenshot application.\nCONCLUSION To be honest, both two applications offer the same things and are valid if you just need to control base functions of your phone and transfer file without connecting it using the USB cable. Personally I prefer AirDrop because you can use it from any device and browser: no additional software is required (Java). Then\u0026hellip; chose your application and use the phone from your browser! :)\n[gallery type=\u0026ldquo;slideshow\u0026rdquo; ids=\u0026ldquo;763,764,760,762,755,753,754,761,757,758,756,759\u0026rdquo;]\n","date":"15 janvier 2013","externalUrl":null,"permalink":"/airdroid-vs-kies-air-control-your-android-with-your-browser/","section":"Posts","summary":"","title":"AirDroid vs Kies Air: control your Android with your browser","type":"posts"},{"content":"A thing I used on my iPhone was the streaming of my photo, video and music on my media server (for me wasn\u0026rsquo;t an Apple TV but an XBMC on a RaspberryPi) using Airplay. So when I switched to Android I directly look to do the same. Naturally, knowing Android is an \u0026ldquo;open\u0026rdquo; system, it wasn\u0026rsquo;t to difficult to find the way to do it.\nFirst of all we need to say we can\u0026rsquo;t stream AirPlay using the embedded android tools. Airplay is (normally) an Apple proprietary protocol and system, so you (normally) should use Apple hardware to have it. After this little introduction, all of us knows that there are many systems/tools on the market that can reproduce an AirPlay server (XBMC for example) or AirPlay client.\nFor the Android I found DoubleTwist. Is not just a program that allow you to stream using AirPlay, but can let you to use your Andoird Phone like an Apple Phone (ok maybe it\u0026rsquo;s not so cool to use android as an iPhone, but if you can\u0026rsquo;t leave the Apple Style\u0026hellip; here you can! ;)). For example you can synch your iTunes music and playlists to the Andoird Application (the player).\nSo, as you can see on the website and as you can understand by what we have said, you have a program for you desktop machine (Mac/Windows) that allow the interaction with the iTunes library, and an app for your android system. If you want you can just use the software on your android phone/tablet, that is a media player with AirPlay streaming function; the desktop app is necessary just if you want to sync your device!\nAnyway, to start using Airplay on your device is really simple. In the android app settings, select to enable streaming (you need the paid app, with the free the function is locked)\nThen, in the player windows you have a new icon, like on the iPhone, if an AirPlay server is detected on your desktop. Clicking on it you have then a popup that allow you to select the target device.\nThat\u0026rsquo;s all!\nA thing I want to say in the end, is that AirPlay it\u0026rsquo;s a good solution if you have an AppleTV as media center; for any other device it\u0026rsquo;s better to use something better (like DLNA). For example on the AirPlay protocol just the Apple allowed video formats can be streamed\u0026hellip; so get better! ;)\n","date":"14 janvier 2013","externalUrl":null,"permalink":"/android-talking-with-your-apple-tv/","section":"Posts","summary":"","title":"Android talking with your Apple TV","type":"posts"},{"content":" 19,000 people fit into the new Barclays Center to see Jay-Z perform. This blog was viewed about 76,000 times in 2012. If it were a concert at the Barclays Center, it would take about 4 sold-out performances for that many people to see it. In 2012, there were 47 new posts, growing the total archive of this blog to 102 posts.\nThe busiest day of the year was October 21st with 707 views. The most popular post that day was Google Chrome for iOS. Link to the complete report here:\nhttp://jetpack.me/annual-report/26723616/2012/\n\u0026nbsp; ","date":"14 janvier 2013","externalUrl":null,"permalink":"/blog-annual-report-76000-visits-in-2012/","section":"Posts","summary":"","title":"Blog Annual Report: 76000 visits in 2012","type":"posts"},{"content":"If you have shutdown problems on your Mac: it never shutdown and is stack at on the grey circular icon. The problem could be your TimeMachine setting.\nI just discovered that my MacBook had problems connecting to the network timemachine disk (it\u0026rsquo;s not the time capsule but a Buffalo NAS)\u0026hellip; No backup were executed, sometimes the system was unusable and cannot shutdown or restart (need to force the shutdown with the power button). Well i don\u0026rsquo;t exactly know the reasons around the missing connection to the disk, but after a reset of my time machine settings, all back to normal.\nSo if you have some strange problems in your Mac, check your latest backup and try to force a new one by hand; if it cannot find/connect to your disk\u0026hellip; you got it! :)\n","date":"14 janvier 2013","externalUrl":null,"permalink":"/cannot-shutdown-macosx-try-changing-timemachine-settings/","section":"Posts","summary":"","title":"Cannot shutdown MacOSX: try changing TimeMachine settings","type":"posts"},{"content":"After I updated my iPad1 (I know it\u0026rsquo;s not the latest apple device, but I\u0026rsquo;m not an apple fanatic :)) to iOS5, and later to iOS 5.0.1, I noticed that was really slow in the normal usage and applications crashed almost once every hour. At the end I had an iPad but it wasn\u0026rsquo;t a pleasure to use it like the past.\nAfter some tests, I start looking for something to fix these problems, knowing that Apple doesn\u0026rsquo;t allow the downgrade to old versions. And the solution I found, that seems to work after some days of tests, is to reset all settings, going to:\nsettings -\u0026gt; general -\u0026gt; reset -\u0026gt; restore all (maybe in English the menu names are not exactly these, but my iPad is in Italian :P)\nWith this reset you don\u0026rsquo;t lose your data (applications, photos, musics, mails, \u0026hellip;) but it just restore the iPad settings to factory default. This means you have to re-enter your wifi key, you have to select your backgrounds but nothing more. An important thing is to prevent restore of these settings from a backup. In fact, after restart, iPad ask you to enter some settings like the first time you start it up: language, location, etc\u0026hellip; And then it will ask you if you\u0026rsquo;d like to restore the device using iCloud or iTunes. Well\u0026hellip; Here you have to say NO ;)\nRight now, my iPad works like with the previous iOS version: not slow, no apps crashes\u0026hellip; At the end I have my iPad back.\nHope this could help someone else.\n","date":"30 novembre 2012","externalUrl":null,"permalink":"/ipad1-slow-down-after-ios5-update-fix/","section":"Posts","summary":"","title":"iPad1 slow down after iOS5 update: FIX","type":"posts"},{"content":"I recently refactor the KermIT project to get a completely dynamic project and have the ability to add plugins.\nHere I\u0026rsquo;m going to show you how to check for \u0026ldquo;installed plugins\u0026rdquo; and automatically configure the urls. First of all you have to configure a Django app, named for example plugins and correctly link it up to your Django project. So add it to INSTALLED_APPS in the settings.py file and add it to global urls.py\nurlpatterns = patterns('', (r'^plugins/', include('webui.plugins.urls')), ) Then in the plugins app create a urls.py module like this one:\nfrom django.conf.urls.defaults import patterns, include import logging from webui.plugins import utils logger = logging.getLogger(__name__) urlpatterns = patterns('', ) installed_plugins = utils.installed_plugins_list() for plugin in installed_plugins: try: urlpatterns += patterns('', (r\"^%s/\" % plugin, include(\"webui.plugins.%s.urls\" % plugin)), ) except: logger.debug (\"Plugin %s does not provides urls\" % plugin) Where my utils.py is the following:\nimport os def installed_plugins_list(): path = os.path.dirname(__file__) installed_plugins = [] for module in os.listdir(path): if os.path.isdir(path + '/' + module) == True: installed_plugins.append(module) return installed_plugins How does it work?\nWell, the utils module will list all packages inside the current one (the plugins in my example), then the urls.py module will just loop on this list and it try to include the submodule urls. If it works (no exception raised) all urls are imported using the plugin name (i.e. /plugins/puppet/*); if you have an exception (no urls configured for that plugin) you just have a log message.\nReally easy, and I can assure it works! :)\n","date":"6 novembre 2012","externalUrl":null,"permalink":"/django-automatically-import-sub-modules-urls/","section":"Posts","summary":"","title":"Django: automatically import sub-modules urls","type":"posts"},{"content":"Today we completed the first big development part of the KermIT project and we can consider it \u0026ldquo;really stable\u0026rdquo; for the production environments. Was stable even with all previouses versions, that means could already be used for production, but with this version we complete many useful things and we automate many setup/installation processes.\nImportant devs in the ChangeLog:\nscript to automatically configure SeLinux rules for KermIT setup script for all post installation operations New version of RestMCO (2.0-5), more flexible using POST messages Completed dev of DynamicGroups with expressions. Expression is anything allowed by mcollective compound filter Complete refactor of Admin Area And more... [video src=\"http://www.mornati.net/video_kermit/KermIT-AdminAreaRefactored.mp4\" width=\"100%\"] [video src=\u0026ldquo;http://www.mornati.net/video_kermit/KermIT-DynamicGroups.mp4\u0026rdquo; width=\u0026ldquo;100%\u0026rdquo;]\n","date":"26 octobre 2012","externalUrl":null,"permalink":"/kermit-webui-new-version-and-the-road-to-20/","section":"Posts","summary":"","title":"KermIT WebUI: new version and the road to 2.0","type":"posts"},{"content":"After the comment I received on my previous post: Chrome on iOS is not really Chrome. I try to investigate and make some tests on the device. The browser is identified, using different JavaScript libraries you can find on the net, as safari unknown version and in the site reported in the photo as Mozilla/5.0 with a safari WebKit. So, as user comment reported, is not really THE Chrome. But (I like to put a \u0026ldquo;but\u0026rdquo; somewhere ;))\u0026hellip; What of the real chrome we are losing? The render engine: on iOS devices Chrome uses the safari one. So any HTML page is rendered in the \u0026ldquo;safari style\u0026rdquo; and not with the chrome one. But safari is not InternetExplorer, so for me it\u0026rsquo;s ok ;), and is as fast as chrome to load pages (on the same Mac). The JavaScript engine: I think the chrome one is the fastest we can find on the market. So it could be considered a great difference with the original Chrome. But, once again, is this really important on your mobile device? And is the safari engine really bad for you?\nFor me the great feature having a \u0026ldquo;fake\u0026rdquo; chrome on my iOS device, is I can have al my bookmarks and preferences automatically imported. So I can check a thing in the evening and bookmark it to have it ready on my desktop on the next work day. Actually I was using some other tools, but the simple bookmark is the fastest one.\nSo, yes, is not the Chrome you have on your desktop. But the iOS is a different and mobile device: some functionalities is better than no one! :)\nA bad thing is that is not completely integrated on your device: you will always find \u0026ldquo;Open with Safari\u0026rdquo; function in any app and you can\u0026rsquo;t change this. Apple decided this, and if you don\u0026rsquo;t like the decision you can buy\u0026hellip; an Android phone ;)\n","date":"22 octobre 2012","externalUrl":null,"permalink":"/chrome-on-ios-6-after-tests/","section":"Posts","summary":"","title":"Chrome on iOS 6: after tests","type":"posts"},{"content":"After many and many, many, \u0026hellip; tests, I finally got a working configuration for my MultiFunction Samsung Printer (SCX 3405W) with \u0026ldquo;Scan to PC\u0026rdquo; button. This button allow a direct scan to a configured computer (Mac or Windows, even if the config in Windows is really simple and automatic) from your printer: you can remain on your printer (scanner), change as many document as you want, and go back to your computer just in the end with all documents saved in the desired format.\nFirst of all you need to install \u0026ldquo;Samsung Easy Printer Manager\u0026rdquo; software on your Mac, start it, and go to Advanced Mode.\n![](/images/samsung-set-scan-to-pc-button-for-mac-osx/00-Schermata-2012-10-12-alle-22_33_12_fhcgcj.png)\n[warning] If you can\u0026rsquo;t reach this window (program crash when you click on the button) like me at the beginning, you should remove it using the Uninstall.sh script included in the downloaded ZIP. Type, in a terminal:\n\u003esh Uninstall.sh [/warning]\nHere you a have to select a folder where you want to put the scanned documents. But, in any manual I found information that images are sent using the samba protocol. This means you should allow sharing on that folder in read/write mode!!\n![](/images/samsung-set-scan-to-pc-button-for-mac-osx/01-Schermata-2012-10-12-alle-22_33_41_jjbilp.png)\nThen you have just to select \"Enable\" radio button at the top of the advanced windows in Easy Printer Manager and click save (if you want, before saving you can change all other parameters you want). When you click save you have a window asking you for an ID to use to identify the computer. With my computer (Italian language with French Keyboard) when I tried to type anything in the textbox I got a message saying that the input was not allowed (?!!?). Here again I finally found that you can just add and use the USA keyboard layout and you can type your ID! ![](/images/samsung-set-scan-to-pc-button-for-mac-osx/02-Schermata-2012-10-12-alle-22_31_15_qdl215.png)\nIf all worked well you should have a popup message saying that all parameters were correctly saved! Then you can go to your printer, click on \"Scan to\" button and magically found your PDF/JPEG/... on the selected folder! That\u0026rsquo;s all!\n[gallery link=\u0026ldquo;file\u0026rdquo; columns=\u0026ldquo;4\u0026rdquo;]\n","date":"11 octobre 2012","externalUrl":null,"permalink":"/samsung-set-scan-to-pc-button-for-mac-osx/","section":"Posts","summary":"","title":"Samsung set \"Scan to PC\" button for Mac OSX","type":"posts"},{"content":"After some days of tests we produces a first working version of oVirt agent for MCollective.\nYou can find sources of agent on github: https://github.com/thinkfr/mcoplugins/blob/master/ovirt.rb\nWhat we want to do wasn\u0026rsquo;t a complete export of all oVirt functions (if you want to configure it in \u0026ldquo;expert\u0026rdquo; mode, it\u0026rsquo;s better to use the oVirt console), but export all the main functions to let you to centralize the control of your virtual farm.\nTo use it you just need to put ovirt.rv and ovirt.ddl file on your ovirt machine (where you have installed the ovirt sdk, and it\u0026rsquo;s not necessary to put it directly on the hypervisor), in /usr/libexec/mcollective/mcollective/agent. Install some gem dependencies: rbovirt (\u0026gt;= 0.0.12) that is the oVirt Ruby API we used as the based of this module and inifile.\nThen you shoud to configure parameters to connect to your oVirt server API. To do this you need to create a file in /etc/kermit/kermit.cfg (why the file is named KermIT? Simply because if few days you will see the oVirt integration in the KermIT webconsole ;)).\n[oVirt] username=admin@internal password=Password api_url=https://server.hostname.net/api And now you are ready to make some tests :)\nhttp://youtu.be/ThwLVH5cm_Q\nYou can find a the HD demo video of the agent here: oVIrt MCollective Agent (webm format)\nThe actual allowed agent actions, that you can find listed into ddl file, are:\nget_api_version: get the oVirt installed API version list_vms: list all defined virtual machines (started up or not) vm_details: show the details of the specified VM get_clusters: get liste of defined clusters in the oVirt farm get_templates: get list of defined templates get_storagedomains: get list of storage domains start_vm: start the specified virtual machine stop_vm: stop the provided virtual machine create_vm: create a new virtual machine add_network: add a new network to a vm add_storage: add a new storage to a vm Updates will sort out shortly with, as I said, a complete integration in the KermIT project. ","date":"8 octobre 2012","externalUrl":null,"permalink":"/mcollective-ovirt-agent/","section":"Posts","summary":"","title":"MCollective oVirt Agent","type":"posts"},{"content":"After iOS6 update my Linux Airprint server does not work anymore for my iDevices. I\u0026rsquo;m referring to my previous article explaining how you can configure a linux machine as Linux Airprint Server!\nFortunately there is no change to the Airprint protocol. Ranil give us the solution (thanks a lot for your test!!)\nYou should add to your previous configuration .service file, into pdl section, image/urf. For example, in my file now I have:\npdl=application/octet-stream,application/pdf,application/postscript,image/gif,image/jpeg,image/png,image/tiff,text/html,text/plain,application/vnd.cups-banner,application/vnd.cups-command,application/vnd.cups-pdf,application/vnd.cups-postscript,image/urf After this your print should be visible in your local network. To completely allow print with any iOS6 application, you have to add also these two files in your cups configuration.\n/usr/share/cups/mime/apple.types\nimage/urf urf (0,UNIRAST) /usr/share/cups/mime/local.convs\nimage/urf application/vnd.cups-postscript 66 pdftops Now your AirPrinter should work correctly!\nThanks a lot Ranil for your help!\nMoved from comment, here the complete Jam guide to setup an AirPrint server for iOS6. Reading the commands executed in the \u0026ldquo;script\u0026rdquo; i can say that is step-by-step guide for a Fedora16/17. For a Centos/RedHat you have to change a little bit some steps.\n#AS ROOT: echo “image/urf urf (0,UNIRAST)” \u0026gt; /usr/share/cups/mime/apple.types echo “image/urf application/vnd.cups-postscript 66 pdftops” \u0026gt; /usr/share/cups/mime/local.convs # pdftops can be installed with: yum install poppler-utils #restore SELINUX permissions restorecon /usr/share/cups/mime/* #restart cups (print server) systemctl restart cups.service #AS USER: #download airprint-generate.py as stated above and run it from: https://github.com/tjfontaine/airprint-generate cd /tmp/ python airprint-generate.py #AS ROOT: mv /tmp/AirPrint-*.service /etc/avahi/services/ restorecon /etc/avahi/services/* #restart the avahi service systemctl restart avahi-daemon.service #check the new AirPort service is running avahi-browse --all # no avahi-browse ?, install it: yum install avahi-tools I think you should say a big thanks to Jam for this script! ;)\n","date":"21 septembre 2012","externalUrl":null,"permalink":"/linux-airprint-server-for-ios6-devices/","section":"Posts","summary":"","title":"Linux Airprint Server for iOS6 devices","type":"posts"},{"content":"In questi giorni, effettuando qualche test sulla mia linea internet, sono capitato su questo sito: http://www.netindex.com che mostra alcuni parametri (medi) sulle connessioni al net nei diversi paesi del mondo.\nPossiamo per esempio vedere che in Groenlandia hanno una connessione ADSL abbastanza di base (ma va beh, in Groenlandia cosa se ne fanno di internet? :)): 6,54Mbit/s\nIn Libia: 7,24Mbit/s\nE in Italia\u0026hellip; 5,43 MBit/s!!!\nMa é possibile che solo le società in Italia non abbiano alcun interesse ad offrire un servizio migliore? Perché nascondersi dietro a \u0026ldquo;In Italia abbiamo molti paesi montani\u0026rdquo;, \u0026ldquo;parecchie zone sono disperse nelle campagna\u0026rdquo;, etc. Non credo che in Groenlandia siano messi molto meglio in quanto a dislocazione! E, giusto per fare un paragone, anche qui in Francia parecchie abitazioni sono disperse in campagna con connessioni ADSL fra 1 e 6 Mbit/s, ma l\u0026rsquo;ADSL ce l\u0026rsquo;hanno tutti!! (e aggiungerei che la media qui, per quanto non brillante rispetto ad altri paesi europei, é di 12,65Mbit/s).\n","date":"12 septembre 2012","externalUrl":null,"permalink":"/it/velocita-medie-accesso-a-internet-italia-vs-resto-del-mondo/","section":"Posts","summary":"","title":"Velocità medie accesso a internet: Italia vs Resto del mondo","type":"posts"},{"content":"KemIT development going ahead adding new functions everyday. Today two new videos showing Tree Filters and Persistent DashBoard.\nA problem with big datacenter in the previous KermIT version was all widgets with classed served inside (for example puppet classes widget). If you want to reach a particular class or server you should look for it by hand (search in any folder to find your server). Now you can simply search the resource you need (folder or server name) and you will be pointed out to matching resources.\n[video src=\u0026ldquo;http://www.mornati.net/video_kermit/video/KermIT%20-%20Filters%20in%20tree%20views.mp4\u0026rdquo; width=\u0026ldquo;100%\u0026rdquo;]\nEven if you could move or change dashboard widgets, all mods was not persisted in the previous KermIT version; a simple page refresh restore to default settings. Now any user can personalize the dashboard changing widget position, colors, titles, etc. and all mods will be persisted (any user has a different dashboard). Cookies required to allow this new function.\n[video src=\u0026ldquo;http://www.mornati.net/video_kermit/video/KermIT%20-%20Persistent%20Dashboard%20(per%20user).mp4\u0026rdquo; width=\u0026ldquo;100%\u0026rdquo;]\nStay tuned because we have some other interesting and useful functions\u0026hellip; Do you want tp create a new server in a click? You should use KermIT ;)\n","date":"25 juillet 2012","externalUrl":null,"permalink":"/kermit-new-filters-and-dashboard/","section":"Posts","summary":"","title":"KermIT: New filters and Dashboard","type":"posts"},{"content":"A new interesting feature we are actually coding in KermIT web application is Dynamic Groups. You can define a rule to group servers (and later, all kind of resources) and will be automatically added to this group if they match the rule. In this way, any new server added (or removed) to KermIT network will appear (or disappear) in the correct group with no manual interaction for the admin user.\n[video src=\u0026ldquo;http://www.mornati.net/video_kermit/video/KermIT%20-%20Dynamic%20Groups.mp4\u0026rdquo; width=\u0026ldquo;100%\u0026rdquo;]\nIn the video you can see how you can create dynamic groups and how servers are added to a group (with a simple refresh operation) but, as I said, this function is still in development and you can actually create dynamic groups based on faster facts.\nStay tuned for news about this cool feature! :D\n","date":"24 juillet 2012","externalUrl":null,"permalink":"/kermit-dynamic-groups-for-resources/","section":"Posts","summary":"","title":"KermIT: Dynamic Groups for resources","type":"posts"},{"content":"These days we are working, on the KermIT\u0026rsquo;s master branch, adding some new interersting features (and refactoring lot of our code). Here a video that shows how you can provision the servers, changing the assigned puppet classes directly within the KermIT web interface.\n[video src=\u0026ldquo;http://www.mornati.net/video_kermit/video/Kermit-Edit_Puppet_Classes_with_Hiera_backend.mp4\u0026rdquo; width=\u0026ldquo;100%\u0026rdquo;]\nWhat you should have is the puppet master configured to use Hiera, that allow you to define the puppet site.pp, not in a statically way, but importing definitions using Hiera. Here my dev file:\nnode default { include sudo } node centos6 inherits default { hiera_include('centos6.mmornati.lan', '') } node puppet inherits default { hiera_include($hostame, '') } What will happen when puppet read this file, is a call to hiera to get information using the machine hostname (or the static string \u0026lsquo;centos6.mmornati.lan\u0026rsquo;).\nTo increase the scalability of our infrastructure, KermIT and Hiera store the puppet configuration into a Redis database. So, when you change server information using KermIT, all stuffs are simply stored into Redis db, and, next time puppet agent is fired, it will read the latest configuration from database.\nAt the moment we don\u0026rsquo;t have a KermIT development RPM, but you can test all these features getting the code directly from github (branch master). On KermIT website an article explains, step by step, how you can install hiera on your system.\n![](/images/kermit-assign-puppet-classes-to-a-server-within-the-webui/00-kermit-hiera_nrksjg.png)\n","date":"19 juillet 2012","externalUrl":null,"permalink":"/kermit-assign-puppet-classes-to-a-server-within-the-webui/","section":"Posts","summary":"","title":"KermIT: assign puppet classes to a server within the webui","type":"posts"},{"content":"Looking for a good \u0026ldquo;OpenID\u0026rdquo; plugin for wordpress for my company website (eh yes, we are using wordpress also for our business website ;)) I found this OneAll Social plugin. And, even if you need to register to an external website that will manage all login requests for you (connections are crypted, so don\u0026rsquo;t worry for your security, or not? :S) I definitely love it! You can find and test it also on this website.\nAfter the plugin installation (think you know how to install plugins in wordpress?) you have a Social Login menu on the left bar in the admin area. Here you can find link to get access to to the external website and create an account there:\n![](/images/oneall-social-plugin-for-wordpress/00-Screenshot-from-2012-07-11-170309_j9us9d.png)\nThen you will get some \"keys\" to paste into API Settings box on this page and (almost) all is done. In the settings area you can decide where you want to show the social buttons, which social networks you want to use, and some other settings always about authentication. But I said almost ready to use, because in fact, any social network need a personal configuration to allow an external application to use it. But, don\u0026rsquo;t worry on the OneAll website you have just to select the social network you want to configure and you have a step by step guide with a complete video that bring you to the correct configuration.\n![](/images/oneall-social-plugin-for-wordpress/01-Screenshot-from-2012-07-11-170413_qcm09i.png)\n![](/images/oneall-social-plugin-for-wordpress/02-Screenshot-from-2012-07-11-170436_mtdbfx.png)\nIf all worked well, but I don't know how you can make some errors with the detailed doc provided by OneAll Social, you can logout from your wordpress website, and on the login page you should see buttons for all the selected social networks. ![](/images/oneall-social-plugin-for-wordpress/03-Screenshot-from-2012-07-11-170048_cqnotr.png)\nAn important thing to say is that users on wordpress are recognized using the mail account (or better, associations from OpenID response and internal users are on username and email fields). This means that maybe, when you test a login you will be logged as new user on your blog. But if you used the same mail account everywhere, you should use what you want and you will be logged as the right user :) Enjoy your new login :)\n[gallery link=\u0026ldquo;file\u0026rdquo; columns=\u0026ldquo;4\u0026rdquo;]\n","date":"10 juillet 2012","externalUrl":null,"permalink":"/oneall-social-plugin-for-wordpress/","section":"Posts","summary":"","title":"OneAll Social Plugin for WordPress","type":"posts"},{"content":"The first rule for a correct backup is to store your backup file(s) on a different server than the one you are backupping. So, even if you can find many wordpress backup plugin opensource, many of those just execute backup in a folder of the same server where you have wordpress installed. So you just remember to download these files on your local computer to have something safe.\nBut, after a little browsing inside all the wordpress plugin I also found some others interesting way to execute your backup (to DropBox, to GDrive, \u0026hellip;), but what I chose in the end is Online Backup. The reason is that, even if it\u0026rsquo;s a free plugin has many power features!\nAfter the installation you have an Online Backup link inside Tools menu in your admin area.\n![](/images/wordpress-online-backup/00-Screenshot-from-2012-07-11-173208_jxxbtg.png)\nYou have just some little steps to configure it: define a schedule for you backup, choosing if you want a full or an incremental backup (the incremental is available just if you select Online backup), setup you online account (optional), define the encryption method and password (optional) and that's all. Yes, we need to create another account for a free online service, but why not? We give our personal information to any kind of supermarket and we almost have nothing back (just a bit of spam). The link for the account registration is directly proposed by plugin and you will have (with the free account) 100mb to use to backup your wordpress (or more than one) blog. ![](/images/wordpress-online-backup/01-Screenshot-from-2012-07-11-173300_hmyneq.png)\nAnd now you can test a manual backup (using the link in the backup menu) to check if all your settings are good. If you will have any kind of problem you can login into Wordpress Online Backup website, give your encryption password and download your full backup (files + database). The backup will not offer a wordpress migration too, but I tested it by hand today, you can edit your sql file and replace all http address in the file with the new one, and wordpress will works without problems!! :) Take care of your blog, keep it backupped :D\n[gallery link=\u0026ldquo;file\u0026rdquo; columns=\u0026ldquo;4\u0026rdquo;]\n","date":"10 juillet 2012","externalUrl":null,"permalink":"/wordpress-online-backup/","section":"Posts","summary":"","title":"Wordpress Online Backup","type":"posts"},{"content":"Today I tested the OneAll social plugin. The only think I noticed that I don\u0026rsquo;t like too much (at least on my personal blog) is that there is no way to lock the automatic user registration. That means a user can, with a social account, create a user for my blog, and, even if he has no right to accomplished operations on the blog\u0026hellip; I don\u0026rsquo;t want it!! :D\nSo I made a little fix in a plugin file, waiting for the official \u0026ldquo;fix\u0026rdquo; to this. Edit communication.php file in /wp-content/plugins/oa-social-login/includes folder. And change the line 172 (before there is a comment New user) with this:\nif (!is_numeric ($user_id) \u0026amp;\u0026amp; get_option('users_can_register')) That means we just check the wordpress main option that allow the user creation. So if you normally allow registration for your site nothing change, but, as by default for wordpress, registrations is locked, plugin will follow this setting!\nHope this could help someone! ;)\n","date":"10 juillet 2012","externalUrl":null,"permalink":"/wordpress-block-automatically-user-creation-in-oneall-social-plugin/","section":"Posts","summary":"","title":"Wordpress: Block automatically user creation in OneAll Social Plugin","type":"posts"},{"content":"I\u0026rsquo;m one of the few people that prefer to use Twitter for personal social network than Facebook. It gives you an anonymous way to follow people that could be really useful if you want, for example, just get information from a journalist or magazine. But, starting today, if you want to add a social touch to your twitter account, you can use this new service, offered (app is completely free) by a group of italian guys: BDayMe.\nThe app is really simple: you can install it from the AppStore (for the moment App is available just for iOS devices, but other devices will be compatible in the future), connect using your twitter account, and\u0026hellip; you are in. Then you can set an automatic message that bdayme server will tweet for you the day of your birthday; see the birthday of your contacts and send them wishes. As I said you add a social touch to your twitter account.\nBut, if you want to know when is your preferred V.I.P. birthday and be the first to greet him/her birthday\u0026hellip; you must install bdayme on your device. The app is completely free, so\u0026hellip; you can make a little test.\nIf you want to add me I\u0026quot;m @marcomornati on twitter ;)\n","date":"31 mai 2012","externalUrl":null,"permalink":"/bdayme-extends-your-twitter-account/","section":"Posts","summary":"","title":"BDayMe, extends your twitter account...","type":"posts"},{"content":"We take our time, or better, Louis worked on it :), to refactor the KermIT project website. Actually created using Octopress blogging engine.\nWe have many other work in progress for KermIT, but, for the moment you can enjoy this first website refactor and some new videos.\n","date":"31 mai 2012","externalUrl":null,"permalink":"/new-website-and-videos-for-kermit-project/","section":"Posts","summary":"","title":"New website and videos for KermIT project","type":"posts"},{"content":"After an hard work of developer, finally Agilo sort out a new completely refactored version of this software. If you want to manage your project with the Agile (and Scrum) methodology, for FREE, this one I think is the best easy solution.\nLooking on the web site you can find now also a PRO version, but fortunately you can still have a free (even with less functionalities) version.\nSome time ago we see how to install trac on BlueHost; now we add to our installation all the Agilo Capabilities. So, we start this guide, supposing that you trac is working on your blue host account.\nYou should start downloading the Agilo OpenSource version: the source code of the project.\nwget http://www.agilofortrac.com/en/download/agilo_source.tar.gz Then decompress the agile sources:\ntar xf agilo-0.9.8.tar and run the installation\ncd agilo-0.9.8 python setup.py install In this example we are supposing that your python binary is the same you have used to install trac. If false use the correct binary name.\nThen we have just to enable Agilo in our trac environment editing the trac.ini config file:\nvi /home2/mornatin/public_html/trac/projects/conf/trac.ini And add this lines at the end of file\n[components] agilo.* = enabled Now we just need to upgrade Trac database and Wiki to add all agile functionalities:\ntrac-admin /home2/mornatin/public_html/trac/projects upgrade trac-admin /home2/mornatin/public_html/trac/projects wiki upgrade That\u0026rsquo;s all. If everything worked well you should see the agile interface accessing to your trac web app.\n","date":"25 mai 2012","externalUrl":null,"permalink":"/install-agilo-open-for-trac-012-on-bluehost/","section":"Posts","summary":"","title":"Install Agilo Open for Trac 0.12 on BlueHost","type":"posts"},{"content":"A simple way to protect your web server today, without creating any time a different user name/password, is to use one of the many openid server available on internet.\nWe can say that everyone today has a gmail (at least one) account, and, if you are one of the person all over the world that does not have a gmail account, we can add Twitter and Facebook and you are surely in.\nUsing a mod_auth_openid you can protect your web server directly in your apache configuration (conf or .htaccess) file. So, to use it you need first, to install this module. Fortunately I found on internet a repository with rpms for Centos5 and Centos6 and I created rpms for Fedora16 (that is my test machine at home). So, for example on F16, you can do:\nyum localinstall http://repos.mornati.net/openid/fedora16/RPMS/libopkele-2.0.4-1.fc16.i686.rpm yum localinstall http://repos.mornati.net/openid/fedora16/RPMS/mod_auth_openid-0.6-3.fc16.i686.rpm I used yum localinstall because libopkele has some dependencies that are resolved (and installed) by yum.\nNow your apache web server is ready to use OpenID. To configure it you can check official doc on the project website. Following an example of my configuration to use Google OpenID.\nCheck your configuration to verify if access restriction override is allowed. For example in /etc/httpd/conf/httpd.conf\n# # Possible values for the Options directive are \"None\", \"All\", # or any combination of: # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews # # Note that \"MultiViews\" must be named *explicitly* --- \"Options All\" # doesn't give it to you. # # The Options directive is both complicated and important. Please see # http://httpd.apache.org/docs/2.2/mod/core.html#options # for more information. # Options Indexes FollowSymLinks # # AllowOverride controls what directives may be placed in .htaccess files. # It can be \"All\", \"None\", or any combination of the keywords: # Options FileInfo AuthConfig Limit # AllowOverride All # # Controls who can get stuff from this server. # Order allow,deny Allow from all The line to check is AllowOverride, you should have AuthConfig (or All like in my example).\nThen you can create a .htaccess file in the folder you want to protect. And put a content like the following\nAuthType OpenID require valid-user AuthOpenIDTrusted ^https://www.google.com/accounts/o8/ud AuthOpenIDSingleIdP https://www.google.com/accounts/o8/id AuthOpenIDAXRequire email http://openid.net/schema/contact/email ilmorna@gmail.com AuthOpenIDAXUsername email The first two line specifies that you want to use OpenID as authentication system and you want a valid-user to allow the access (that just means the user should be authenticated on gmail). Then, the property AuthOpenIDSindleIdP specify to the mod_auth_openid the address to use for the authentication (here is the google one); the property AuthOpenIDAXRequire, as product doc says, can be used to verify some openid parameters, like the email, first and last name, birth date, etc. It\u0026rsquo;s important if you really want to protect your server because, without this, any person that has a gmail account (and if you remember we supposed that anyone today has this kind of account) can access to your \u0026ldquo;web pages\u0026rdquo;. In my example I get the check the email parameter with the \u0026ldquo;regex\u0026rdquo;; is not really a regex with a single mail specified, but you can create a line like:\nAuthOpenIDAXRequire email http://openid.net/schema/contact/email [mail@gmail.commail2@gmail.commail3@gmail.com] or, if you have a google apps account\nAuthOpenIDAXRequire email http://openid.net/schema/contact/email @businessmail\\.com$ Here you can find description for any other OpenID available parameter if you need other access rules.\nAfter this little conf, if you try to get access to to your protected page, you will be redirected to gmail login, or to a gmail page asking for access if you are already logged in into gmail account. After the gmail login open id parameters are sent to your server and checked with your access rules. If all is passed \u0026ndash;\u0026gt; Access to page.\n","date":"22 mai 2012","externalUrl":null,"permalink":"/apache-and-modauthopenid-on-f16centos/","section":"Posts","summary":"","title":"Apache and mod_auth_openid on F16/Centos","type":"posts"},{"content":"With MCollective version 2.0 we have now support to control Windows Servers. The only thing you must take care is that you need to update all your infrastructure to mco 2.0 because you cannot control servers that have a different version installed.\nThis allow us to complete the support of our KermIT project, that is actually (we have many enhancements in development ;)) a web interface to control mcollective infrastructure adding windows support. The \u0026ldquo;problem\u0026rdquo; was that no windows package was available during our tests and, knowing that usual production environment servers could not have access to internet, this could cause installation problem.\nFor this reason we developed and create an installer for windows server using Rake (make for ruby project) and Inno Setup to create the final .exe setup file.\nIf you want to test it you can find the installer (and source for Rake file) here.\nEnjoy and report us any problem.\n","date":"18 mai 2012","externalUrl":null,"permalink":"/mcollective-20-and-windows-client-installer/","section":"Posts","summary":"","title":"MCollective 2.0 and Windows Client Installer","type":"posts"},{"content":"With @LouisCoilliot we are preparing some new videos that will be used to explain kermit\u0026rsquo;s main functionalities; basically will be a doc extension. Naturally you could find all the videos on the KermIT website (we are actually preparing the new version).\nKermIT: execute basic operations\nKermIT: Post installation steps\nKermIT: Server details\nStay tuned for many others KermIT news!!\n","date":"15 mai 2012","externalUrl":null,"permalink":"/kermit-new-videos-documentation/","section":"Posts","summary":"","title":"Kermit: new videos documentation","type":"posts"},{"content":"If, like me, you used to work on many computers, you should reconfigure anything to allow all your machines. Today I was stucked on heroku repository clone (problem with ssh key on my work laptop).\nTo add new machine to your heroku account (after heroku package installation, using gem for example), you should just use\nmmornati@notebook projects$ heroku keys:add Found existing public key: /home/mmornati/.ssh/id_rsa.pub Uploading SSH public key /home/mmornati/.ssh/id_rsa.pub And, if everything worked well, you should have access to your repository:\nmmornati@notebook projects$ git clone git@heroku.com:mmornatibot.git Cloning into 'mmornatibot'... remote: Counting objects: 226, done. remote: Compressing objects: 100% (219/219), done. remote: Total 226 (delta 41), reused 141 (delta 2) Receiving objects: 100% (226/226), 95.28 KiB | 50 KiB/s, done. Resolving deltas: 100% (41/41), done. ","date":"10 mai 2012","externalUrl":null,"permalink":"/add-new-machine-to-heroku-project/","section":"Posts","summary":"","title":"Add new machine to Heroku project","type":"posts"},{"content":"By default, when you configure a Google account on your iOS device, there is just your default calendar synchronized with iPhone/iPad. But if you have, like me, configured and imported some other calendars that are displayed on your gcal, you surely would have them on your portable device too.\nFor example, in my account, I\u0026rsquo;ve import end my Google Apps (professional) calendar, French/Italian holidays calendar, and some other.\nTo configure what you want to have on your iOS device too, you should visit the mobile sync page (from your device):\nhttp://m.google.com/sync\nIf you have your google account with a language different from english (I just tested with french and italian, so maybe some other languages works without this tips), you should see an \"error\" page\n![](/images/sync-others-google-calendar-to-your-ios-device/00-Photo-10-05-12-23-04-31_x4dgen.png)\nThe error page says google sync is not supported on your device. And even if you retry to refresh the url, you have always the error. As we already (partially) said, the problem is not your device but the language used to show the page. If you click on \"change language\" (naturally link is displayed in your language, in the image is \"modifica lingua\") and select \"English US\" you can see the real sync page.\n![](/images/sync-others-google-calendar-to-your-ios-device/01-Photo-09-05-12-08-51-37_ieangl.png)In my example, I've already two devices configured with my sync account, so in the page I can see sync devices and I can select the one I'd like to configure. If you have never configured your device you should directly access to config page.\n![](/images/sync-others-google-calendar-to-your-ios-device/02-Photo-10-05-12-23-04-54_kpr6ta.png)Here you have just to check the calendars you'd like to synchronize to your iOS device and save the changes.\nYou can check if all worked well accessing the device calendar configuration app:\n![](/images/sync-others-google-calendar-to-your-ios-device/03-Photo-10-05-12-23-05-28_sphjkw.png)Completed!\nEnjoy your sync.\n","date":"9 mai 2012","externalUrl":null,"permalink":"/sync-others-google-calendar-to-your-ios-device/","section":"Posts","summary":"","title":"Sync others google calendar to your iOS device","type":"posts"},{"content":"I finally had time to test the latest version of XBMC media center (version 11) on my Fedora 16. My first test was using directly the rpm provided on rawhide repositories (fedora and rpm-fusion-free) but in this way many others components will be updated (like gnome for example) because the package is produced for Fedora 17.\nSo, following this little guide that provides all necessary steps, I built XBMC directly on my Fedora. The only things to add at the end, is to export the lib folder for the normal user (at least, on my fedora xbmc didn\u0026rsquo;t work without this step).\nSo, for example, you can edit your .bashrc file:\nmmornati@desktop ~$ pwd /home/mmornati mmornati@desktop ~$ vi .bashrc Adding this line at the end of the file\nexport LD_LIBRARY_PATH=\"/usr/local/lib\":$LD_LIBRARY_PATH And then you can startup you XBMC without problem :D\nAs you can see in the following picture, it\u0026rsquo;s really simple to use XBMC as AirPlay server (for videos, photos and music).\n","date":"5 avril 2012","externalUrl":null,"permalink":"/linux-airplay-server-using-xbmc-11/","section":"Posts","summary":"","title":"Linux Airplay server using XBMC 11","type":"posts"},{"content":"I just discovered and tested a nice project that allow you to create an AirPlay server to use as a simple speaker for your iOS device (for example send audio from your iPod to your Linux PC): Shairport!!\nTo install and use it on Fedora 16 distribution is really simple. First of all you should install all the required packages to build this project:\nyum install openssl-devel libao libao-devel perl-Crypt-OpenSSL-RSA perl-IO-Socket-INET6 perl-libwww-perl avahi-tools Then, after a little clone of the git repository\ngit clone https://github.com/albertz/shairport.git You can enter in the shairport directory and build it\nmmornati@desktop shairport$ make cc -O2 -Wall -DHAIRTUNES_STANDALONE hairtunes.c alac.o -o hairtunes -lm -lpthread -lssl -lcrypto -lao cc -O2 -Wall -c socketlib.c -o socketlib.o cc -O2 -Wall -c shairport.c -o shairport.o cc -O2 -Wall -c hairtunes.c -o hairtunes.o cc -O2 -Wall socketlib.o shairport.o alac.o hairtunes.o -o shairport -lm -lpthread -lssl -lcrypto -lao Now you can simply startup the shairport script and check if all works well using your iOS device\nmmornati@desktop shairport$ perl shairport.pl Established under name 'D2908EECAA5A@ShairPort 3882 on desktop' requesting resend on 1 packets (port 53568) ","date":"21 mars 2012","externalUrl":null,"permalink":"/shairport-turn-your-linux-in-an-airplay-speaker/","section":"Posts","summary":"","title":"Shairport: turn your linux in an AirPlay speaker","type":"posts"},{"content":"I noticed that some one arrived on this blog using \u0026ldquo;how to use epomodoro eclipse plugin\u0026rdquo; as search term. Sometimes I think things all always simple, but, even for simple things it\u0026rsquo;s better to have a little guide.\nSo here you are some instructions!\nhttps://github.com/mmornati/epomodoro/wiki/ePomodoro-Guide\n","date":"8 mars 2012","externalUrl":null,"permalink":"/epomodoro-user-guide/","section":"Posts","summary":"","title":"ePomodoro: user guide","type":"posts"},{"content":"I just released the new ePomodoro version. It includes:\nMinor bugfixing Message Receiver Listener activated also when Team Status view is not active/visible Restyled user message box (to send a message to user you can select it on the TeamStatus table and then chose Send Message using the right-click context menu) You can find it on the ePomodoro update site (I noticed that you can't browse the repository if you are using Google Chrome or Safari, don't know why), or download it from Google Code ePomodoro page. And, starting today, you can find it on the Eclipse Marketplace too (I think you should use the update site even if you pass for the marketplace).\nSources are available on GitHub and Google Code.\n","date":"7 mars 2012","externalUrl":null,"permalink":"/epomodoro-version-104-on-the-eclipse-marketplace/","section":"Posts","summary":"","title":"ePomodoro: version 1.0.4 on the Eclipse Marketplace","type":"posts"},{"content":"Since today you can install ePomodoro directly using the provided update site! And naturally update it using the Eclipse function Check for Update.\n![](/images/epomodoro-update-site/00-ePomodoro7_xvrnat.png)\nYou can add this Update Site: http://repos.mornati.net/eclipse/ and select ePomodoro. Should work, even if at the moment you get a message saying the content is not signed, bla, bla, bla. I\u0026rsquo;ll fix it next days, but let me know if you can install ePomodoro without problems.\n","date":"5 mars 2012","externalUrl":null,"permalink":"/epomodoro-update-site/","section":"Posts","summary":"","title":"ePomodoro: update site","type":"posts"},{"content":"I\u0026rsquo;ve just released the ePomodoro 1.0.3.\nThis version includes:\nBugfixes in communication stuffs Changes to UI interfaces Added status bar timer management (no more view required) Added single user message (shown just when selected user is not on Pomodoro) Added configuration of connection parameters in preference page (you can choose manyally the bind address if it does not work) You can download the binary directly on GitHub or GoogleCode (sources are on both server): https://github.com/mmornati/epomodoro/downloads\nhttp://code.google.com/p/e-pomodoro/downloads/list\nEnjoy your Pomodoro work! ;)\n[gallery link=\u0026ldquo;file\u0026rdquo;]\n","date":"1 mars 2012","externalUrl":null,"permalink":"/epomodoro-updates-version-103/","section":"Posts","summary":"","title":"ePomodoro: updates. Version 1.0.3","type":"posts"},{"content":"Yesterday I talked about an Eclipse plugin to use with Pomodoro Technique created by me just to have a broadcast communication for dev team with information about your Pomodoro.\nToday I spent one hour more to \u0026ldquo;complete\u0026rdquo; the first version of this plugin, that is now completely usable for your time management. It is really simple, composed by just two View. Team Status view: a table with your team pomodoro information; CountDown Timer: view where you can manage you Pomodoro; start, pause and reset your timer. In this version you can decide, in the settings page, to auto start your pause timer after the work one, or manage all your timers by hand.\nHave a good Pomodoro ;)\n","date":"25 février 2012","externalUrl":null,"permalink":"/epomodoro-completed-version-10/","section":"Posts","summary":"","title":"ePomodoro: completed version 1.0","type":"posts"},{"content":"After I exaplained to my actual working team how they can be more productive using the Pomodoro Technique, they spent some minutes looking for an utility to use a countdown clock.\nYes guys, we are Geek! Even if we can use anything else as timer, we always look for something cool to install on our PC :D Someone ask me if there was anything for team work: a way to show the Pomodoro timer of the others in your team to know when you could talk with them. And, after minutes of search on the net without results, I decided to spend a couple of hours to create an example of the proposed Team Pomodoro :D\nSo, here you are: ePomodoro! It\u0026rsquo;s an Eclipse Plugin, so you can install it directly in your Eclipse environment (in the future I could create a stand alone application for all non-eclipse developers or non-developers ;)) In Windows-\u0026gt;Preferences Menu you can change some plugin settings like: Team Name and Pomodoro Timer. Team Name allows different team in your society: you will get just messages from your team!\nAt the moment, even if it works (both as a countdown clock and team message), it\u0026rsquo;s just a simple raw plugin to demostrate how easy is to create something like this using the JGroups library as message broadcaster. I\u0026rsquo;ll take some of my time to add cool functions to it in the next days.\n[gallery link=\u0026ldquo;file\u0026rdquo; columns=\u0026ldquo;2\u0026rdquo;]\n","date":"25 février 2012","externalUrl":null,"permalink":"/epomodoro-eclipse-plugin-for-pomodoro-technique-with-team-communication/","section":"Posts","summary":"","title":"ePomodoro: Eclipse Plugin for Pomodoro Technique with Team Communication","type":"posts"},{"content":"Some weeks ago I found PC Monitor application, a new cool way to control your Linux and windows computer. Looking on the product website you can find for server program for Linux and windows (both 32bit and 64bit OS) and clients programs for Android, Windows Phone and iOS devices.\nI tested for some days the Linux server version, that is really easy to install, configure and start. it\u0026rsquo;s a java program so just the JDK is required and then you have a service file to start the server up. On the client (iOS in my tests) you have many real time information about your pcs.\nCheck the gallery for some amazing screenshots. ","date":"17 février 2012","externalUrl":null,"permalink":"/pc-monitor-control-your-computer-from-everywhere-and-with-any-device/","section":"Posts","summary":"","title":"PC Monitor: control your computer from everywhere and with any device","type":"posts"},{"content":"Server control and provisioning is everyday more important due to server farms \u0026ldquo;growth\u0026rdquo;. In reality today server farms are not too big, but using virtualization systems you can have many machines in a single server. So, the problem is (was!!) a way to create or recreate our virtual or physical machines and a simple way to control them.\nTo accomplished these operations you can find many products on the market, both open source and closed source. My preferred tools are: Puppet, for the provisioning part, and Mcollective for the control part. The thing I didn\u0026rsquo;t love was that to use these tools you have to execute commands within the CLI (I love CLI tools, but it\u0026rsquo;s difficult to sell products to our customers without anything cool to show. That the reason why Apple does a lot of money today: same thing that the others but with a cool interface ;))\u0026hellip; anyway, for this reason we start thinking to an interface to help us use mcollective (and behind to use puppet too), and the implementation we propose is: KermIT. A complete and customizable web interface to control and provision your servers.\nKermit actually offers many functionalities and we are adding everyday something new. The actual version allow you to discover your mcollective \u0026ldquo;clients\u0026rdquo; (machines you can can control), on any machine, it will discover installed agent with all actions (the things you can execute on that machine) and propose you all these operations on the web interface with, if needed, a form to ask you parameters for the execution.\nFor example, if you want to execute a \u0026ldquo;service httpd start\u0026rdquo; operation, in kermit you just select the target server (or you can execute the commands on ALL servers at the same time), select the service agent with the start action, and the interface will propose a window asking you the service name. Nothing to configure, except mcollective with the proper certificates (you can find all instruction on Kermit documentation or directly on mcollective documentation for the client configuration).\nWe also developed platforms to control (and customize) services execution. For example JBoss or PostgreSQL platforms allow you to execute a deploy operation, proposing you autofilled fields using the target server information (you don\u0026rsquo;t have to fill up all fields by hand, but kermit will ask to the target server, for example, the list of available jboss instances). A complete and customizable, within the web interface, ACL system for application security. You can protect any server, agent and operations using a single username or a group name, so you have the ability to allow critical operations just to administrator (i.e. a developer can deploy a new war in jboss but can\u0026rsquo;t execute any other operation, and can operate just on development machines).\nAnd many many others things\u0026hellip;\n[tube]http://www.youtube.com/watch?v=WMZodfLfzBw\u0026amp;list=PLE6AD5E02BB4B773D\u0026amp;index=4\u0026amp;feature=plpp_video[/tube]\nYou can access to other videos on my YouTube page.\nWe are actually updating and refactoring kermit after a complete tests on a hundreds servers farm\u0026hellip; so stay tuned for any update (like the kermit website ;)). Naturally you can install, use and test it using provided RPM repositories (for EL5 at the moment but soon for EL6 too) or, if you prefer, using sources.\nAny comment is welcome. :)\n[gallery link=\u0026ldquo;file\u0026rdquo; columns=\u0026ldquo;4\u0026rdquo;]\n","date":"28 janvier 2012","externalUrl":null,"permalink":"/kermit-a-webui-for-mcollective/","section":"Posts","summary":"","title":"KermIT: a WebUI for MCollective","type":"posts"},{"content":"Dopo aver testato per l\u0026rsquo;ennesima volta il JailBreak untethered (questa volta per iOS5) e aver reputato l\u0026rsquo;assoluta inutilità della cosa (soprattutto che in 3 ore di utilizzo \u0026ldquo;normale\u0026rdquo; la springboard si é impallata e resettata almeno 5 volte) ho deciso di ripristinare l\u0026rsquo;iPhone 4 senza usare un precedente backup, che credo mi portassi appresso dalla versione 3 (ed era per il vecchio 3GS)\u0026hellip; Il ripristino l\u0026rsquo;avevo in realtà fatto perché é la procedura per \u0026ldquo;liberarlo\u0026rdquo; dal blocco su scheda Orange (dopo aver fatto richiesta ad Orange che chiede ad Apple di sbloccare il telefono) ma, con mia grande sorpresa, la batteria del mio telefono adesso dura incredibilmente taaaanto :D\nCome da foto, sono a 20 ore di stanby più 1 ora di utilizzo ed ho ancora il 90% della mia batteria!! Prima del ripristino perdevo un buon 20% della batteria nello stanby durante la notte.\nQuindi, se avete dei problemi di durata della batteria e proprio non riuscite ad uscirne, provate con un bel ripristino (senza riprendere nessun backup che va a rimettere nella vostra home tutti i settings di tutte le applicazioni che avete installato nel tempo, e che magari non avete più sul vostro telefono).\nUn\u0026rsquo;altra cosa che avevo notato é che l\u0026rsquo;applicazione di Google+ si prende non poca batteria anche durante lo standby. Puo\u0026rsquo; essere solo un caso del mio telefono, o una configurazione errata prima del ripristino, ma già togliendo solo l\u0026rsquo;applicazione avevo notato un leggero miglioramento. A voi i test\u0026hellip;.\n![20120123-211239.jpg](http://blog.mornati.net/wp-content/uploads/2012/01/20120123-211239.jpg)\nEdit bis: lo attacco a caricare ma ecco i risultati! Ottimo!!!\n![20120124-133808.jpg](http://blog.mornati.net/wp-content/uploads/2012/01/20120124-133808.jpg)\n","date":"21 janvier 2012","externalUrl":null,"permalink":"/it/durata-batteria-iphone-4-meglio-dopo-ripristino/","section":"Posts","summary":"","title":"Durata batteria iPhone 4: meglio dopo ripristino","type":"posts"},{"content":"After some requests from Nikos on the Trac post I noticed that the article I wrote to use Trac on BlueHost cannot work anymore. As I said in this post BlueHost decided to remove the Python 2.6 installed by default, and the previous guide was based on that version of Python. Anyway thanks to Nikos for this new post ;)\nAfter the installation of version 2.7.2 (or the one you prefer) of python as you can see in the previously linked article, it\u0026rsquo;s really simple to install Trac too.\nFirst of all you \u0026ldquo;need\u0026rdquo; to install easy_install for your version of python (it\u0026rsquo;s not true that you need but it\u0026rsquo;s the fastest way to install python libraries).\nwget http://peak.telecommunity.com/dist/ez_setup.py python ez_setup.py Or naturally use Python2.7 command if you don\u0026rsquo;t have your version as default in your shared host console.\nNow you can install Trac with all required dependencies. Differently from my previous Trac post, now you don\u0026rsquo;t need to specify the installation directory because the home directory of your python version is already in your home folder. This means you can install Python library directly in the python folder (without root permission)\neasy_install-2.7 Genshi easy_install-2.7 Babel==0.9.5 easy_install-2.7 Trac If you have trac (and/or any other library) installed for python 2.6 you can purge out the installation directory (in any case you can\u0026rsquo;t use these library anymore).\nrm -rf .local/lib/python2.6 Now if installation worked good you should have access to trac-admin command, and, as the previous guide you can create your trac environment.\ntrac-admin /home2/mornatin/public_html/trac/kermit initenv cd /home2/mornatin/public_html/trac trac-admin ./kermit/ deploy ./ cp cgi-bin/trac.fcgi ./ The .htaccess file configuration is the one you can read in the previous Trac article.\nOptions -Indexes RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /cgi-bin/trac.fcgi/$1 [L,QSA] RewriteRule ^$ cgi-bin/trac.fcgi [L] All should be configured correctly and you should have access to your trac using your browser.\nIf have problems running trac-admin command you can have some different problems:\nYour python path is not correctly exported. Check if you have a configuration like this in your .bashrc file export PATH=$HOME/python272/bin:$HOME/.local/bin:$HOME/.local/usr/bin:$PATH You had installed Trac for python 2.6 and when you run trac-admin command you receive an error message about python 2.6 binary. You need to remove trac-admin symbolic link in .local/bin folder. rm .local/bin/trac-admin ","date":"12 janvier 2012","externalUrl":null,"permalink":"/trac-012-on-bluehost-with-python-27/","section":"Posts","summary":"","title":"Trac 0.12 on BlueHost with Python 2.7","type":"posts"},{"content":"I just discovered an amazing (free) Wordpress theme for iPad (maybe it works for other tablets too, but at the moment I\u0026rsquo;ve no way to test with anything different than iPad. I know, it\u0026rsquo;s not so useful to use a specific theme for a tablet, because the normal site, if it is well formatted, it\u0026rsquo;s perfectly shown on tablet screen; but in this way your blog/website is shown as \u0026ldquo;native tablet application\u0026rdquo;: read articles on your blog will be like read an ebook :)\nAnyway, if you want to make some tests, the plugin I found is Onswipe you can see a working version browsing my blog on a tablet (let me know if you can see it with an android tablet :P). You can check result looking the photo in this article.\nA thing I\u0026rsquo;m thinking about is that in this way (iPad theme, mobile theme, normal theme) it\u0026rsquo;s impossible to control how your articles will be shown to the users\u0026hellip; I check some articles I wrote and seem ok an all devices, but I\u0026rsquo;ve just simple text articles with some \u0026ldquo;code\u0026rdquo; information (well displayed just on the normal website).\nIs it a good choice to have separated themes?\n","date":"2 janvier 2012","externalUrl":null,"permalink":"/onswipe-wordpress-ipad-theme/","section":"Posts","summary":"","title":"Onswipe: Wordpress iPad theme","type":"posts"},{"content":"Recently GitHub has released sources about two \u0026ldquo;internal\u0026rdquo; projects: hubot and janky. So it\u0026rsquo;s time to start using them (or just testing them to imagine a future usage).\nAfter some tests and research I find a proper way to install hubot and use it as IRC bot, but I decide to report the complete procedure here because all the guides I found on the net was outdated or not complete (missing always some important step). The best one the guide me on the right way is the one you can find here, but I report the instructions also on this blog post.\nFirst of all you need to install hubot dependencies on your \u0026ldquo;build\u0026rdquo; machine (for me a Fedora 16). Node.js The fast way to install node (or at least my preferred way to install stuffs) on your Fedora is the one described on this page\nsudo yum localinstall --nogpgcheck http://nodejs.tchol.org/repocfg/fedora/nodejs-stable-release.noarch.rpm sudo yum install nodejs and then the npm (node package manager)\nsudo yum install npm Has reported on this Hubot ticket could you have a problem installing npm that does not install the required coffe script. To do this, as reported, you should run:\nnpm install -g coffee-script Thanks to Hardy for this fix!\nHeroku To make a first faster test on node.js apps the Heroku service is the best one. You can install on your machine the heroku command that hep you to interact with your account (naturally you need to create an account on heroku, free for a basic service like hubot).\ngem install heroku To accomplished this step you the required dependencies are: ruby and ruby gem (on fedora 16 packages are rubygems-1.8.10-1.fc16.noarch and ruby-1.8.7.352-1.fc16)\nNow you are ready to start working on Hubot. Knowing we are real geeks we will work with the master version (directly from github latest sources :))\ngit clone git://github.com/github/hubot.git cd hubot npm install bin/hubot --create ../kermitbot With these commands you will download sources (git package is required on your machine), install all node required packages (defined in package.json file) and, with the first line, you will say that you want to create a hubot clone to put in ../kermitbot directory. By now all things should be done in your \u0026ldquo;kermitbot\u0026rdquo; directory (we will work directly on our personalized hubot).\nInstall IRC dependency The best way to test a bot (without creating other chat accounts or buying anything strange) is using an irc channel: easy to create, completely free, so\u0026hellip; way not? :) To do this, the next step is to add to our bot a dependency to node irc module.\nvi package.json #in the dependencies area of this file your should have \"dependencies\": { \"hubot\": \"2.0.7\", \"hubot-scripts\": \"2.0.2\", \"optparse\": \"1.0.3\", \"hubot-irc\": \"0.0.6\" } The important line (the one you should add to your package file) is the hubot-irc with the version you want to use.\nNow all should be ready for a first commit on heroku and the first test\ngit init . git add . git commit -m \"My Hubot initial commit\" Then create a repository on your heroku account with\nheroku create kermitbot --stack cedar And push your bot to heroku\ngit push heroku master master should be used for the first commit (to create the master branch on your git repository) and then you can push without specifying it. After the commit you should see in your console the heroku log saying that your nodejs app is being configured and all necessary dependencies will be installed.\nConfiguring your Hubot Now, always using heroku command to interact with your account, you can specify some variables that are required to configure your bot. In this case the variables are for irc modules saying the irc server and channel and your bot username:\nheroku config:add HUBOT_IRC_NICK=\"kermitbot\" heroku config:add HUBOT_IRC_ROOMS=\"#kermit-webui\" heroku config:add HUBOT_IRC_SERVER=\"irc.freenode.net\" You app should be automatically started, but in case you can run a command to start it\nheroku ps:scale app=1 You can check status for your app looking to the process or reading the app log\nheroku logs heroku ps If all works well, accessing to your channel (#kermit-webui in this example) you should see your bot connected to your account. NB if you select a really generic name for your bot (like hubot), maybe you see something different connected to your channel, like hubot7, this because username on irc server must be unique, so it will change the hubot name until it can find a free one.\nConfigure startup option To startup your hubot with the irc module enable you have to modify Procfile file putting replacing the app line with:\napp: bin/hubot -a irc After this mod you should push it to heroku to make it available\nTesting your Hubot Now you have installed your bot you can start interacting with it (if you follow this example without adding other modules you will have just some basic operation). To do this, in your channel you can write:\nhubot help tweet - Returns a link to a tweet about is a badass guitarist - assign a role to a user is not a badass guitarist - remove a role from a user animate me - The same thing as `image me`, except adds a few convert me to - Convert expression to given units. help - Displays all of the help commands that Hubot knows about. help - Displays all help commands that match . image me - The Original. Queries Google Images for and map me - Returns a map view of the area returned by `query`. math me - Calculate the given expression. mustache me - Searches Google Images for the specified query and mustache me - Adds a mustache to the specified URL. pug bomb N - get N pugs pug me - Receive a pug ship it - Display a motivation squirrel show storage - Display the contents that are persisted in redis That will produce an help list with any available command. To execute command you can write (in main chat or with a direct message): hubot command parameters so, for example to show you a place on a google map you can make a query like\nhubot map me redhat headquarter That will show you the map url.\nEasy, isn\u0026rsquo;t it? :D\nProblems Just to point out a possible problem, if you have modules that required redis (key, value store) you should have a pay account on heroku and so you will have an error accessing redis in your log. To use it with basic modules and on irc you can ignore this error.\nChange hubot name Your hubot answer just when you call it by name, and by default the name is naturally hubot. You can change this name modifying Procfile with this info:\napp: bin/hubot -a irc --name kermitbot --enable-slash (@Deprecated: The \u0026ndash;enable-slash property allow you to talk to your bot just using a / and not with the full name.) As reported on the Hubot trac, the \u0026ndash;enable-slash is deprecated in the latest version of hubot, you should replace it by \u0026ndash;alias\napp: bin/hubot -a irc --name kermitbot --alias '/' /mustache me kermit webui ","date":"20 décembre 2011","externalUrl":null,"permalink":"/install-hubot-as-irc-channel-bot/","section":"Posts","summary":"","title":"Install Hubot as IRC channel bot","type":"posts"},{"content":"Anytime I installed a new version of Fedora (I used to make fresh installation to prevent any possible package errors/compatibility) I spent lot of my time reconfiguring my installation with the stuffs: installing additional repository, configuring flash player (hoping it will die in the future), install java, \u0026hellip;, \u0026hellip;\nThis time I discovered surfing on internet Fedora Utils, and incredible project (simple but useful) that make all this configuration for you. You have just to select what you want and it will install everything for you. I know, it\u0026rsquo;s not to difficult to create a simple bash to do the same thing, but I\u0026rsquo;m too lazy to lose time configuring my home desktop :)\nTest it and enjoy.\n","date":"18 décembre 2011","externalUrl":null,"permalink":"/fedora-utils-tweaks-for-your-preferred-distro/","section":"Posts","summary":"","title":"Fedora Utils: tweaks for your preferred distro","type":"posts"},{"content":"In this latest version of Fedora distribution, developers decided to format disk using GPT label on it. The problem is that many BIOS can\u0026rsquo;t recognize disk as bootable after the installation, so that you cannot access to your new system. After hours spent trying to fix my installation (without success), I noticed that on the knowing bugs page there was the real solution to the problem, even if, in my opinion, it\u0026rsquo;s not well explained.\nAnyway, if you want to install Fedora 16 without spend time later fixing the boot problem or re-installing it, the solution is to force the installation procedure to use the normal partitioning system, so you can let Fedora decide how to format your disk (as usual, that is my preferred installation way: decide all after).\nAdd the nogpt property to anaconda before starting the installation procedure. To do this you should see in your DVD menu (just after the boot) a voice saying something mike \u0026ldquo;add properties\u0026rdquo; or \u0026ldquo;change properties\u0026rdquo;. You have just to select it, write nogpg and startup the installation. Then you can install your Fedora normally.\nDon\u0026rsquo;t know why the Fedora team decided to use this system in the latest version, but in any case, I think wasn\u0026rsquo;t a good idea. Many users, that don\u0026rsquo;t want to lose time around problems like this, or don\u0026rsquo;t know linux very well to try to fix the problem manually, simply run away from this distribution: Ubuntu it\u0026rsquo;s simple to install and to manage after installation. In my opinion this is absolutely the worst decision dev team could take!\n","date":"29 novembre 2011","externalUrl":null,"permalink":"/fedora-16-boot-problem-after-install/","section":"Posts","summary":"","title":"Fedora 16 boot problem after install","type":"posts"},{"content":"After I discovered the presence of Python 2.6 on BlueHost, they decided to remove this installation by default. Fortunately it\u0026rsquo;s really simple to build Python from sources and install it (and naturally, the good thing is that all required packages to build Python are installed on BlueHost servers). So here the steps to follow to build and install the python version you prefer (tested with Python 2.6 and 2.7.x).\nwget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tgz tar xzvf Python-2.7.2.tgz and, just a note, the package is well done and it will create a Python subfolder :)\nAfter this we can already configure and install it.\ncd Python-2.7.2 ./configure -prefix=/home2/mornatin/python272 --enable-unicode=ucs4 make make install Change the Python version in this example and the installation directory with what you prefer. Naturally, considering you are on shared host (if you have a dedicated server you can install python using your distribution package system), you have access only to your home folder, so the target directory must be inside your home. If all worked well, at the end of this procedure your python is correctly install in your system and you can start using it. To test you can just simply try to start the binary file\n/home2/mornatin/python272/bin/python A thing I can suggest, if you don\u0026rsquo;t want to override the BlueHost default python and/or if you want to install different python version, is to rename the python binary with something different. For example:\nmv /home2/mornatin/python272/bin/python /home2/mornatin/python272/bin/python27 After this step you can add the python bin folder to your PATH and use it everywhere:\nexport PATH=/home2/mornatin/python272/bin:$PATH All configured and you can start working with your new python version. An important thing to remember is that, if you haven\u0026rsquo;t python 2.7 (or other) configured as default python, when you want to install a new module in it, you should invoke the correctly binary file. Following this installation example:\npython27 setup.py install ","date":"29 novembre 2011","externalUrl":null,"permalink":"/install-python-27-on-bluehost/","section":"Posts","summary":"","title":"Install Python 2.7 on BlueHost","type":"posts"},{"content":"After a while I didn\u0026rsquo;t use VirtualBox, the virtualization system I use just to host my Windows virtual machines (all others machines are on KVM), I discovered that I cannot build the DKMS kernel module and so no way to start my Windows virtual machine. Not a big problem, I know :) But sometimes I need to test web application or build procedure on Windows environment.\nAfter some tests I discovered in the build log file the real problem about the VirtualBox build procedure:\nfatal error: asm/amd_iommu.h: No such file or directory compilation terminated. so, it cannot find the kernel asm/amd module. And the problem is exactly that in the latests version of the kernel this module is removed (or renamed, not sure exactly), but VirtualBox want to use it.\nSo a quick-and-dirt solution I found is to copy this module from the previous version of kernel. For me it was:\n[root@mmornati 2.6.41.1-1.fc15.x86_64]# cp /usr/src/kernels/2.6.40.4-5.fc15.x86_64/arch/x86/include/asm/amd_iommu.h /usr/src/kernels/2.6.41.1-1.fc15.x86_64/arch/x86/include/asm/ After this I build the VirtualBox module without problem (/etc/init.d/vboxdrv setup) and started up my virtual machine.\n","date":"28 novembre 2011","externalUrl":null,"permalink":"/virtualbox-linux-dkms-module-build-problems/","section":"Posts","summary":"","title":"VirtualBox Linux: DKMS module build problems","type":"posts"},{"content":"Premessa: il problema l\u0026rsquo;ho riscontrato solo su un iPhone 4 (su 2) e non so dire se sia successo qualcosa di anomalo durante la procedura d\u0026rsquo;update (niente di evidente almeno).\nDopo aver aggiornato l\u0026rsquo;update (in realtà alla prima necessità dell\u0026rsquo;agenda, quindi dopo un paio di giorni dall\u0026rsquo;update :)) mi sono accorto che stranamente i contatti non erano stati risoncronizzati con il telefono (NB i contatti non erano realmente sincronizzati né attraverso iTunes né usando Exchange/Google, quindi erano solo all\u0026rsquo;interno dei backup). La cosa molto strana é che in realtà nelle chiamate perse e nei preferiti, sebbene la rubrica fosse vuota, si vedevano i nomi dei contatti.\nDopo aver giochicchiato un po\u0026rsquo; con le impostazioni ho scoperto che il problema era solo legato ad iCloud (baco??): avevo scelto di non effettuare il backup su iCloud.\nNelle impostazioni risultavano però selezionati alcuni dei servizi di cui effettuare il backup (fra cui i contatti). In pratica\u0026quot;l\u0026rsquo;interruttore\u0026quot; generale per andare su iCloud era spento ma tutto il resto no.\nApparentemente questo ha perturbato un po\u0026rsquo; la rubrica. Per far tornare visibili tutti i contatti ho disattivato temporaneamente, nel menu iCloud, la sincronizzazione contatti, per poi riattivarla subito dopo e\u0026hellip; magia, ecco ancora tutti i miei contatti (che ripeto erano solo nascosti).\n[gallery link=\u0026ldquo;file\u0026rdquo; columns=\u0026ldquo;4\u0026rdquo;]\n","date":"18 octobre 2011","externalUrl":null,"permalink":"/it/ios-contatti-persi-dopo-update-alla-5/","section":"Posts","summary":"","title":"iOS: Contatti persi dopo update alla 5","type":"posts"},{"content":"Ad un giorno dal lancio del nuovo iOS, la domanda che ci si pone è sempre la stessa: ma l\u0026rsquo;autonomia non si è ridotta? Molto spesso è solo un\u0026rsquo;impressione, visto che l\u0026rsquo;autonomia della batteria dipende da molti fattori, ma ho scoperto un piccolo \u0026ldquo;cavillo\u0026rdquo; nella nuova versione del sistema Apple che fa effettivamente consumare la batteria più velocemente di quanto non dovrebbe.\nIn pratica il sistema GPS resta sempre attivo (anche non mostrando l\u0026rsquo;icona) per la localizzazione del fuso orario (funzionalità forse utilissima negli States ma qui in Europa poco ci frega!).\nLa prima cosa da fare e far mostrare sempre l\u0026rsquo;icona del GPS anche per quei servizi considerati di sistema, cosi da avere sotto controllo quando effettivamente il vostro GPS è attivo. Per far ciò andare in: impostazioni -\u0026gt; Localizzazione -\u0026gt; Servizi di sistema ed attivare la voce icona barra di stato Dovreste subito veder comparire l\u0026rsquo;icona della localizzazione in alto a destra. A questo punto, per disattivare l\u0026rsquo;inutile servizio, nello stesso menù disattivate la voce Imposto fuso orario.\nDovreste aver ridato ore di vita alla vostra batteria! :)\n","date":"13 octobre 2011","externalUrl":null,"permalink":"/it/ios5-migliorare-lautonomia-della-batteria/","section":"Posts","summary":"","title":"iOS5: migliorare l'autonomia della batteria","type":"posts"},{"content":"If you have an XChat instance running on a server 24 hours a day and you access it once a day (or less like me), you should receive lot of private messages (or Direct Messages using IRC naming) that you will see after days. So, to allow you to directly receive a notify for any Direct Message (both for main chat and private one), you can add to your XChat a simple script to forward any message to another service (like EMail, Twitter, Facebook or what you prefer). Here I'll show a script to send a message privately to you on twitter.\n# XChat Twitter DM notify plugin # Copyright (C) 2011 Marco Mornati \u0026lt;ilmorna@gmail.com\u0026gt; # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License # for more details. # # This script will check any chat message to you in your xchat (in DirectMessage are or in main chat directed to you) # and will send a direct message to the configured twitter account. # To use it you need to create a new application in your twitter account to retrieve consumer_key, secrets and access information # Your application should naturally has read and write access to your twitter account # module_name = \u0026ldquo;twitternotify\u0026rdquo; module_version = \u0026ldquo;0.1\u0026rdquo; module_description = \u0026ldquo;Notify direct messages to twitter\u0026rdquo;\nimport os import twitter pid = os.getpid()\nimport xchat xchat.prnt(module_description + \u0026quot; loaded\u0026quot;)\ndef send_dm_twitter(word): api = twitter.Api(consumer_key=\u0026lsquo;yourkey\u0026rsquo;, consumer_secret=\u0026lsquo;yoursecret\u0026rsquo;, access_token_key=\u0026lsquo;acctoken\u0026rsquo;, access_token_secret=\u0026lsquo;accsecret\u0026rsquo;) credentials = api.VerifyCredentials() if credentials: print \u0026ldquo;Logged as %s\u0026rdquo; % credentials.name\nstatus = api.PostDirectMessage('twitter_nick', '%s: %s' % (word[0], word[1])) print status.created_at def focus_cb(word, word_eol, userdata): send_dm_twitter(word) return xchat.EAT_NONE\ndef highlight_cb(word, word_eol, userdata): send_dm_twitter(word) return xchat.EAT_NONE\ndef private_cb(word, word_eol, userdata): send_dm_twitter(word) return xchat.EAT_NONE\nxchat.hook_print(\u0026ldquo;Focus Tab\u0026rdquo;, focus_cb) xchat.hook_print(\u0026ldquo;Channel Action Hilight\u0026rdquo;, highlight_cb) xchat.hook_print(\u0026ldquo;Channel Msg Hilight\u0026rdquo;, highlight_cb) xchat.hook_print(\u0026ldquo;Private Message\u0026rdquo;, private_cb) xchat.hook_print(\u0026ldquo;Private Message to Dialog\u0026rdquo;, private_cb)\nThe only things you have to do are:\nCreate a new application in your twitter account (going to OAuth page). All key and secrets must be substituted where you initialize your twitter Api (twitter.Api line on the scripts). A things to remember is the application you are creating must has read and write access to your account Change the target ussername (in the script is twitter_nick). For example, you if you want to receive a private message to your twitter account when a DirectMessage is sent to you in xchat, here you have to put your twitter username. Install script in your xchat (usually $HOME/.xchat2 folder). You can also test the script just loading id using the menu voice Load plugin or script That's all. If all worked well, you can test sending a private message to you on IRC and you should receive a private message on twitter :D Let me know if you have any better idea to do the same thing, or if you have problem and/or fix on the proposed script.\n","date":"4 octobre 2011","externalUrl":null,"permalink":"/xchat2-script-post-to-twitter-1/","section":"Posts","summary":"","title":"XChat2 script: post to Twitter","type":"posts"},{"content":"If you have an XChat instance running on a server 24 hours a day and you access it once a day (or less like me), you should receive lot of private messages (or Direct Messages using IRC naming) that you will see after days. So, to allow you to directly receive a notify for any Direct Message (both for main chat and private one), you can add to your XChat a simple script to forward any message to another service (like EMail, Twitter, Facebook or what you prefer). Here I'll show a script to send a message privately to you on twitter.\n# XChat Twitter DM notify plugin # Copyright (C) 2011 Marco Mornati \u0026lt;ilmorna@gmail.com\u0026gt; # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License # for more details. # # This script will check any chat message to you in your xchat (in DirectMessage are or in main chat directed to you) # and will send a direct message to the configured twitter account. # To use it you need to create a new application in your twitter account to retrieve consumer_key, secrets and access information # Your application should naturally has read and write access to your twitter account # module_name = \u0026ldquo;twitternotify\u0026rdquo; module_version = \u0026ldquo;0.1\u0026rdquo; module_description = \u0026ldquo;Notify direct messages to twitter\u0026rdquo;\nimport os import twitter pid = os.getpid()\nimport xchat xchat.prnt(module_description + \u0026quot; loaded\u0026quot;)\ndef send_dm_twitter(word): api = twitter.Api(consumer_key=\u0026lsquo;yourkey\u0026rsquo;, consumer_secret=\u0026lsquo;yoursecret\u0026rsquo;, access_token_key=\u0026lsquo;acctoken\u0026rsquo;, access_token_secret=\u0026lsquo;accsecret\u0026rsquo;) credentials = api.VerifyCredentials() if credentials: print \u0026ldquo;Logged as %s\u0026rdquo; % credentials.name\nstatus = api.PostDirectMessage('twitter_nick', '%s: %s' % (word[0], word[1])) print status.created_at def focus_cb(word, word_eol, userdata): send_dm_twitter(word) return xchat.EAT_NONE\ndef highlight_cb(word, word_eol, userdata): send_dm_twitter(word) return xchat.EAT_NONE\ndef private_cb(word, word_eol, userdata): send_dm_twitter(word) return xchat.EAT_NONE\nxchat.hook_print(\u0026ldquo;Focus Tab\u0026rdquo;, focus_cb) xchat.hook_print(\u0026ldquo;Channel Action Hilight\u0026rdquo;, highlight_cb) xchat.hook_print(\u0026ldquo;Channel Msg Hilight\u0026rdquo;, highlight_cb) xchat.hook_print(\u0026ldquo;Private Message\u0026rdquo;, private_cb) xchat.hook_print(\u0026ldquo;Private Message to Dialog\u0026rdquo;, private_cb)\nThe only things you have to do are:\nCreate a new application in your twitter account (going to OAuth page). All key and secrets must be substituted where you initialize your twitter Api (twitter.Api line on the scripts). A things to remember is the application you are creating must has read and write access to your account Change the target ussername (in the script is twitter_nick). For example, you if you want to receive a private message to your twitter account when a DirectMessage is sent to you in xchat, here you have to put your twitter username. Install script in your xchat (usually $HOME/.xchat2 folder). You can also test the script just loading id using the menu voice Load plugin or script That's all. If all worked well, you can test sending a private message to you on IRC and you should receive a private message on twitter :D Let me know if you have any better idea to do the same thing, or if you have problem and/or fix on the proposed script.\n","date":"4 octobre 2011","externalUrl":null,"permalink":"/xchat2-script-post-to-twitter/","section":"Posts","summary":"","title":"XChat2 script: post to Twitter","type":"posts"},{"content":"An annoying feature of recent iOS is the AirPrint capability! I\u0026rsquo;m saying that is annoying because you can print from your iOS device only to enabled printers. Today this feature is added to lot of printers, but maybe (like in my situation) means: change my printer with a new one.\nConsidering that I\u0026rsquo;m using printer just in rare situations (like print online flying tickets) is not reasonable to change it! So, looking on internet, I found that you can easily create an AirPrint printer server using a native application for MacOSX/Windows or, if you have a linux home server like me, the avahi service included in linux distributions.\nTo configure your avahi service with your printer you can use this python script: https://github.com/tjfontaine/airprint-generate with this simple command\npython airprint-generate.py that will automatically look in your linux cups configuration, extract your printer and generate the file for avahi. If you have more than one printer configure you can pass a parameter to the script saying witch printer you want to configure.\nIf all works well you should have a file with a name like this: AirPrint-EPSONDX5000.service containing all required information. Now, just copying this file in the avahi service folder, you will enable your printer:\nmv AirPrint-EPSONDX5000.service /etc/avahi/services/AirPrint-EPSONDX5000.service If all works well you should have this on your iOS device:\nNOTE: I noticed with some avahi version there is problem discovering printers: printer is shown in your iOS device just for a couple of minute and then you cannot see it anymore. To fix this problem I just added in crontab (run every minute):\ntouch /etc/avahi/services/AirPrint-EPSONDX5000.service I know that is not a really cool solution, but I didn\u0026rsquo;t found anything better. Actually on my Fedora the problem with amahi seems fixed, but \u0026ldquo;remember the touch\u0026rdquo; if you have problems ;)\nUPDATE: Following the matt suggestion in the comments, you can edit your iptables firewall rules allowing multicast DNS traffic (mDNS). For example add in your /etc/sysconfig/iptables file, this line\n-A RH-Firewall-1-INPUT -p udp --dport 5353 -d 224.0.0.251 -j ACCEPT Using an iptables -L you should then see a line like this:\nACCEPT udp -- anywhere 224.0.0.251 udp dpt:mdns Thanks a lot matt for your help!!!\n","date":"27 septembre 2011","externalUrl":null,"permalink":"/linux-as-airprint-server/","section":"Posts","summary":"","title":"Linux as AirPrint server","type":"posts"},{"content":" Today we will see how to install gitweb on our (shared) host (I\u0026rsquo;m always talking about host because all the tests are done on my shared host service, always Bluehost ;), most of all because if you want to install a service like this, on your personal server you can simply install it by RPM/DEB package).\nThe big problem is where I can find gitweb? Directly within the git sources ;) This means if you have installed git from sources you have already build gitweb too and you just need to install it.\nmornatin@mornati.net [~/git-2011-09-07/gitweb]# ll total 300 drwxr-xr-x 3 mornatin mornatin 4096 Sep 16 12:51 ./ drwxr-xr-x 19 mornatin mornatin 12288 Sep 14 14:31 ../ -rw-r--r-- 1 mornatin mornatin 18130 Aug 30 13:35 INSTALL -rw-r--r-- 1 mornatin mornatin 5508 Aug 30 13:35 Makefile -rw-r--r-- 1 mornatin mornatin 18849 Aug 30 13:35 README -rwxr-xr-x 1 mornatin mornatin 231347 Aug 30 13:35 gitweb.perl* drwxr-xr-x 3 mornatin mornatin 4096 Sep 14 14:31 static/ Here you can see a not-built git web project, located in \u0026ldquo;latest\u0026rdquo; git sources (git-2011-09-07) gitweb folder. So, if you just want to install gitweb without git (for example to get latest version of gitweb without changing your git) you can enter this directory and run a simple make.\nmornatin@mornati.net [~/git-2011-09-07/gitweb]# make SUBDIR ../ make[1]: `GIT-VERSION-FILE' is up to date. GEN gitweb.cgi Now, il all worked well, looking in gitweb folder, you can find a cgi file.\nmornatin@mornati.net [~/git-2011-09-07/gitweb]# ll total 536 drwxr-xr-x 3 mornatin mornatin 4096 Sep 16 12:59 ./ drwxr-xr-x 19 mornatin mornatin 12288 Sep 14 14:31 ../ -rw-r--r-- 1 mornatin mornatin 815 Sep 16 12:59 GITWEB-BUILD-OPTIONS -rw-r--r-- 1 mornatin mornatin 18130 Aug 30 13:35 INSTALL -rw-r--r-- 1 mornatin mornatin 5508 Aug 30 13:35 Makefile -rw-r--r-- 1 mornatin mornatin 18849 Aug 30 13:35 README -rwxr-xr-x 1 mornatin mornatin 231143 Sep 16 12:59 gitweb.cgi* -rwxr-xr-x 1 mornatin mornatin 231347 Aug 30 13:35 gitweb.perl* drwxr-xr-x 3 mornatin mornatin 4096 Sep 14 14:31 static/ What you need to do now is just to copy the cgi script in your apache and all static files (in the static folder inside the gitweb one).\ncp *.cgi /home/user/public_html/git cp static/* /home/user/public_html/git Now you have to configure your gitweb service creating the file gitweb_config.perl in the same place of cgi file (in this example /home/user/public_html/git). In this file you can cut\u0026amp;paste this code\n# where is the git binary? $GIT = \"/usr/bin/git\"; # where are our git project repositories? $projectroot = \"/home/user/repositories\"; # what do we call our projects in the gitweb UI? $home_link_str = \"My gitweb service\"; # where are the files we need for gitweb to display? @stylesheets = (\"gitweb.css\"); $logo = \"git-logo.png\"; $favicon = \"git-favicon.png\"; # what do we call this site? $site_name = \"My Gitweb\"; Where you have to specify: the location of git bin, the place of your git repository (the root directory where all git projects are located, git web will check for git repository starting from this path), and optionally some style stuffs and descriptions).\nThe only thing remaining is the configuration of your .htaccess file (or a httpd/conf.d/*.conf file if you have root access to your server). You can configure like this one adding a basic authentication to create a private gitweb service\nAuthType Basic AuthName \"git repository\" AuthUserFile \"/home/user/passwd\" require valid-user Options +ExecCGI RewriteEngine On RewriteRule ^$ gitweb.cgi RewriteRule ^([?].*)$ gitweb.cgi$1 The important things to enable gitweb is starting from Options line.\nNow you can use gitweb and start browse your projects. Enjoy!\n","date":"15 septembre 2011","externalUrl":null,"permalink":"/install-gitweb-on-your-host/","section":"Posts","summary":"","title":"Install Gitweb on your host","type":"posts"},{"content":"After the installation of git on my bluehost account I tried to figure out a good way to create and access to my git repository. Even if I thought the Apache bridge was the best way to access to git files, I found that on bluehost, the best and fastest way is directly using the ssh protocol. So here explained the method I choose to create and use a private git repository on my shared account.\nFirst of all, to simplify the repository creation process I added to .bashrc file a new function:\nnewgit() { if [ -z $1 ]; then echo \"usage: $FUNCNAME project-name.git\" else gitdir=\"/home2/mornatin/repositories/$1\" mkdir $gitdir pushd $gitdir git --bare init git --bare update-server-info cp hooks/post-update.sample hooks/post-update chmod a+x hooks/post-update touch git-daemon-export-ok popd fi } The operations to execute every time (and done automatically by the previous function) are:\ncreate the project folder initialize a git bare repository update-server-info to update your git config file enable the default post-update hook create a file to enable the export of the bare repository Now to create your repository you can simply run on the server: newgit test.git and a test.git repository is created in your default location (defined in the bashrc function). To test it you can simply try to clone repository on your \"development\" machine: mmornati-macbook:~ mmornati$ git clone ssh://mornatin@mornati.net/~/repositories/test.git Errors? If the response is no.... DONE! :) The only things to remember is that the first commit on your project (test.git in this example), requires the specification of branch you want to work with, so you need to run commands like the following:\ntouch README git add . git commit -m \"Init repo\" git push -u origin master The important thing is just the line with push. After this first commit/push you can work normally using git pull and git push and all your files will be sent on master branch of your repository.\n","date":"14 septembre 2011","externalUrl":null,"permalink":"/create-git-repository-on-shared-host/","section":"Posts","summary":"","title":"Create Git repository on shared host","type":"posts"},{"content":"Ecco un\u0026rsquo;interessante ricerca condotta usando Google: il nostro futuro svelato fino al 2101!\n![](/images/our-future-revealed-by-google/00-future_timeline_cdziof.png)\nFonte: xkcd ","date":"13 septembre 2011","externalUrl":null,"permalink":"/it/our-future-revealed-by-google/","section":"Posts","summary":"","title":"Our future revealed by Google","type":"posts"},{"content":" Siamo in Francia ormai da 7 mesi e qualche giorno, quindi direi che é giunto il momento di raccontare qualche peripezia della vita oltralpe.\nCominciano dalle basi: la sécurité sociale (la A.S.L. francese, per l\u0026rsquo;appunto). In Italia basta che uno abbia la residenza da qualche parte sul territorio e automaticamente gli viene attribuito un codice fiscale e gli viene donata una tessera sanitaria nazionale; i residenti, insomma, hanno il braccialetto dell\u0026rsquo;all-inclusive :). Qui invece le cose sono un \u0026ldquo;pelo\u0026rdquo; diverse.\nSenti parlare per la prima volta della sécurité sociale quando devi firmare il tuo contratto di lavoro: per poter essere assunto devi avere il numero magico. Perfetto, vado a chiederlo\u0026hellip; Alla sécurité sociale, per poter presentare la richiesta bisogna fornire un serie di documenti fra cui le ultime 3 buste paga!! Eh si, per poter avere la copertura sanitaria é necessario lavorare ed averlo fatto continuativamente negli ultimi 3 mesi (un modo come un altro per dire che hai i rimborsi sanitari solo se paghi le tasse). Che cosa interessante\u0026hellip; e quindi?\nFortunatamente il genio che ha inventato il sistema ha visto subito il \u0026ldquo;potenziale\u0026rdquo; baco e creato la scappatoia: il datore di lavoro può assumerti facendo una richiesta specifica alla sécurité sociale per avere il tuo numero. Bella li\u0026hellip;\nNel frattempo però, la bella notizia é che devo comunque lavorare per 3 mesi prima di avere la copertura sanitaria: se hai bisogno paga (poi forse ti rimborsiamo). Poco male direte voi: quante volte ti capita di dover andare dal medico o aver bisogno di visite specialistiche? Fortunatamente (quasi) mai, per me, ma con un figlio di pochi mesi é capitato regolarmente almeno 1 volta al mese da quando siamo qui! Per fortuna la moglie é francese e\u0026hellip; no, fortunatamente niente! Negli ultimi anni non ha abitato sul territorio francese e non ha pagato le tasse in Francia quindi la copertura sanitaria é stata sospesa\u0026hellip; in attesa di dimostrare il ritorno in patria con 3 magiche buste paga!! Quindi riassumendo: nessuna copertura per noi due e il figlio minore deve essere attaccato all\u0026rsquo;assicurazione di uno dei due genitori; quindi per tre mesi \u0026ldquo;al tram\u0026rdquo; :)\nPagando, pagando, arriviamo ad avere le tre buste paga necessarie e, tornando agli uffici della sécurité sociale, presentiamo finalmente la nostra domanda, convinti che il numero (paragonabile al nostro codice fiscale visto che viene generato sulla base dei dati anagrafici aggiungendo un codice alla fine per evitare i doppioni) ci venga assegnato immediatamente. No, ci vorrà almeno un mese!\nMorale: se dovete trasferirvi in Francia per lavoro, interesse o quello che vi pare, fatelo solo se contate di essere in buona salute per i primi 4 mesi!\nAl di la dell\u0026rsquo;umorismo, tutte le eventuali spese sanitarie che affronti nei mesi di \u0026ldquo;non copertura\u0026rdquo; verranno rimborsate successivamente (un bel bonifico sul tuo conto e via), quindi il disagio é solo parziale, se non guadagni solo lo stretto necessario per vivere. In questo modo però il sistema sanitario si assicura di non sperperare soldi con e per persone che dichiarano il falso. Potete andare in un ospedale italiano e dichiarare, quando vi viene chiesto di pagare il ticket, che siete disoccupato. Avrete la vostra visita gratuitamente perché, anche grazie alla legge sulla privacy, gli ospedali non hanno modo di verificare se quello che dite é vero.\n","date":"6 septembre 2011","externalUrl":null,"permalink":"/it/paese-che-vai-usanza-che-trovi-asl/","section":"Posts","summary":"","title":"Paese che vai, usanza che trovi: \"A.S.L.\"","type":"posts"},{"content":"I\u0026rsquo;m going ahead testing installation of tools I need on my shared host. Today I take my time to test Trac 0.12, an enhanced wiki and issue tracking system for software development.\nNaturally, using this version require Python 2.6 (that fortunately I\u0026rsquo;ve ready-to-use on BlueHost), and, like the Trac guide says, you need Genshi and Babel installed to use it. So, this time, we will try to use easy_install to simplify our installation. With easy_install in fact, you can just specify the name of the package you want to install, and will be automatically downloaded, with any required dependencies, and then installed. Let\u0026rsquo;s go\u0026hellip;\n[user@ci-server ~]# easy_install-2.6 --install-dir $HOME/.local/lib/python2.6/site-packages/ Genshi Searching for Genshi Reading http://pypi.python.org/simple/Genshi/ Reading http://genshi.edgewall.org/ Reading http://genshi.edgewall.org/wiki/Download Best match: Genshi 0.6 Downloading http://ftp.edgewall.com/pub/genshi/Genshi-0.6-py2.6.egg Processing Genshi-0.6-py2.6.egg Moving Genshi-0.6-py2.6.egg to /home2/mornatin/.local/lib/python2.6/site-packages Adding Genshi 0.6 to easy-install.pth file Installed /home2/mornatin/.local/lib/python2.6/site-packages/Genshi-0.6-py2.6.egg Processing dependencies for Genshi Finished processing dependencies for Genshi And now Babel, required at version 0.9.5.\n[user@ci-server ~]# easy_install-2.6 --install-dir $HOME/.local/lib/python2.6/site-packages/ Babel==0.9.5 Searching for Babel==0.9.5 Reading http://pypi.python.org/simple/Babel/ Reading http://babel.edgewall.org/ Reading http://babel.edgewall.org/wiki/Download Best match: Babel 0.9.5 Downloading http://ftp.edgewall.com/pub/babel/Babel-0.9.5-py2.6.egg Processing Babel-0.9.5-py2.6.egg creating /home2/mornatin/.local/lib/python2.6/site-packages/Babel-0.9.5-py2.6.egg Extracting Babel-0.9.5-py2.6.egg to /home2/mornatin/.local/lib/python2.6/site-packages Adding Babel 0.9.5 to easy-install.pth file Installing pybabel script to /home2/mornatin/.local/lib/python2.6/site-packages/ Installed /home2/mornatin/.local/lib/python2.6/site-packages/Babel-0.9.5-py2.6.egg Processing dependencies for Babel==0.9.5 Finished processing dependencies for Babel==0.9.5 Now, we can try install Trac, always using easy_install (but if you prefer, following the Django guide, you can install it using the \u0026ldquo;normal\u0026rdquo; python installation procedure). In this way, without specifying the version, you will be sure to have the latest available (latest stable).\n[user@ci-server ~]# easy_install-2.6 --install-dir $HOME/.local/lib/python2.6/site-packages/ Trac Searching for Trac Reading http://pypi.python.org/simple/Trac/ Reading http://trac.edgewall.org/ Reading http://trac.edgewall.org/wiki/TracDownload Reading http://projects.edgewall.com/trac Reading http://projects.edgewall.com/trac/wiki/TracDownload Reading http://trac.edgewall.com/ Best match: Trac 0.12.2 Downloading ftp://ftp.edgewall.com/pub/trac/Trac-0.12.2.zip Processing Trac-0.12.2.zip Running Trac-0.12.2/setup.py -q bdist_egg --dist-dir /tmp/easy_install-gZb1BF/Trac-0.12.2/egg-dist-tmp-_EJVF6 catalog 'trac/locale/vi/LC_MESSAGES/messages.po' is marked as fuzzy, skipping catalog 'trac/locale/fa/LC_MESSAGES/messages.po' is marked as fuzzy, skipping catalog 'trac/locale/el/LC_MESSAGES/messages.po' is marked as fuzzy, skipping Adding Trac 0.12.2 to easy-install.pth file Installing trac-admin script to /home2/mornatin/.local/lib/python2.6/site-packages/ Installing tracd script to /home2/mornatin/.local/lib/python2.6/site-packages/ Installed /home2/mornatin/.local/lib/python2.6/site-packages/Trac-0.12.2-py2.6.egg Processing dependencies for Trac Finished processing dependencies for Trac Done! You trac is installed. Now you need just some other little things to see it in action. First thing, create a trace project.\n[user@ci-server ~]# trac-admin /home2/mornatin/public_html/trac/kermit initenv Creating a new Trac environment at /home2/mornatin/public_html/trac/kermit Trac will first ask a few questions about your environment in order to initialize and prepare the project database. Please enter the name of your project. This name will be used in page titles and descriptions. Project Name [My Project]\u0026gt; Kermit Please specify the connection string for the database to use. By default, a local SQLite database is created in the environment directory. It is also possible to use an already existing PostgreSQL database (check the Trac documentation for the exact connection string syntax). Database connection string [sqlite:db/trac.db]\u0026gt; Creating and Initializing Project Installing default wiki pages TracRevisionLog imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracRevisionLog TracNotification imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracNotification SandBox imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/SandBox InterTrac imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/InterTrac InterWiki imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/InterWiki TracImport imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracImport TracTicketsCustomFields imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracTicketsCustomFields TracSupport imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracSupport WikiDeletePage imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiDeletePage TracModWSGI imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracModWSGI WikiStart imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiStart TracQuery imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracQuery TitleIndex imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TitleIndex TracRoadmap imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracRoadmap TracIni imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracIni TracBrowser imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracBrowser PageTemplates imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/PageTemplates TracUnicode imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracUnicode TracReports imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracReports TracInstall imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracInstall InterMapTxt imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/InterMapTxt WikiRestructuredText imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiRestructuredText TracWiki imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracWiki WikiProcessors imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiProcessors WikiHtml imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiHtml TracInterfaceCustomization imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracInterfaceCustomization TracLinks imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracLinks TracTickets imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracTickets TracBackup imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracBackup TracLogging imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracLogging WikiNewPage imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiNewPage TracUpgrade imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracUpgrade WikiRestructuredTextLinks imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiRestructuredTextLinks TracFineGrainedPermissions imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracFineGrainedPermissions TracChangeset imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracChangeset CamelCase imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/CamelCase TracRss imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracRss TracRepositoryAdmin imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracRepositoryAdmin TracSearch imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracSearch TracAdmin imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracAdmin TracNavigation imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracNavigation TracWorkflow imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracWorkflow RecentChanges imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/RecentChanges TracModPython imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracModPython TracGuide imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracGuide WikiPageNames imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiPageNames TracPlugins imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracPlugins TracPermissions imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracPermissions TracTimeline imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracTimeline WikiMacros imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiMacros TracStandalone imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracStandalone TracEnvironment imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracEnvironment TracFastCgi imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracFastCgi TracAccessibility imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracAccessibility TracCgi imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracCgi TracSyntaxColoring imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/TracSyntaxColoring WikiFormatting imported from /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/wiki/default-pages/WikiFormatting --------------------------------------------------------------------- Project environment for 'Kermit' created. You may now configure the environment by editing the file: /home2/mornatin/public_html/trac/kermit/conf/trac.ini If you'd like to take this new project environment for a test drive, try running the Trac standalone web server `tracd`: tracd --port 8000 /home2/mornatin/public_html/trac/kermit Then point your browser to http://localhost:8000/kermit. There you can also browse the documentation for your installed version of Trac, including information on further setup (such as deploying Trac to a real web server). The latest documentation can also always be found on the project website: http://trac.edgewall.org/ Congratulations! Now we have to link Trac with apache (I\u0026rsquo;m always on shared host, and, even to test thing, I cannot open port different by default http/https). Using trac-admin you can invoke the fact.cgi script creation with command:\n[user@ci-server ~]# trac-admin ./kermit/ deploy ./ Copying resources from: trac.web.chrome.Chrome /home2/mornatin/.python-eggs/Trac-0.12.2-py2.6.egg-tmp/trac/htdocs /home2/mornatin/public_html/trac/kermit/htdocs Creating scripts. where, the first folder is the trac repository you have created before and the second one is the apache folder. Scripts (fastCGI, CGI and WSGI) are created inside cgi-bin subfolder. If you make executable the script you want to use (chmod +x script-name) you can already test your trac installation using a url like http://www.yoursite.com/trac/cgi-bin/trac.fcgi.\nNaturally is not to cool to use an url like that one, so we can configure a .htaccess to invoke our script.\nOptions -Indexes RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /cgi-bin/trac.fcgi/$1 [L,QSA] RewriteRule ^$ cgi-bin/trac.fcgi [L] Finished. Access the folder where you have created your .htaccess file and you should see your trac running.\n","date":"5 septembre 2011","externalUrl":null,"permalink":"/install-trac-012-in-shared-host/","section":"Posts","summary":"","title":"Install Trac 0.12 in shared host","type":"posts"},{"content":"The next step (after the Python 2.6 discovered) is to install Django in my shared host. Naturally my user account is not a sudoers and I don\u0026rsquo;t have the root password, so the installation using the server package system is not possible.\nFortunately any Python project is quite simple to install, and Django is a Python project :D\nwget http://www.djangoproject.com/download/1.3/tarball/ tar xzvf Django-1.3.tar.gz cd Django-1.3 python2.6 setup.py install --user Note: I used python2.6 command to show out I want to install and use it with that version of Python. Naturally if you have an alias python should be sufficient. Same thing if you want to install it using Python 2.4, in this case the \u0026ndash;user option does not work, so the right command to run is\npython setup.py install --home $HOME/.local Then, add yur Django installation to your user PATH, setting it in .bashrc file. In your home folder run:\nvi .bashrc export PATH=$HOME/.local/bin:$HOME/.local/usr/bin:$PATH After this two simple steps Django is ready and you can create your first project (or install your existing project). Supposing you have your Django project installed into ~/projects/kermit-webui to make it accessible from browser you need two components configured. The first thing is a Python fastcgi script to load your Django application, the second thing is the .htaccess file to configure your Apache and load the script. First of all we create the public_html folder where we put our two components mkdir ~/public_html/kermit cd ~/public_html/kermit Here we will create the fastcgi script vi kermit.fcgi #!/usr/bin/python2.6 import sys, os\nAdd a custom Python path. # sys.path.insert(0, \u0026ldquo;/home/user/.local/lib/python2.6\u0026rdquo;) sys.path.insert(13, \u0026ldquo;/home/user/projects/kermit\u0026rdquo;) os.environ[\u0026lsquo;DJANGO_SETTINGS_MODULE\u0026rsquo;] = \u0026ldquo;webui.settings\u0026rdquo; from django.core.servers.fastcgi import runfastcgi runfastcgi(method=\u0026ldquo;threaded\u0026rdquo;, daemonize=\u0026ldquo;false\u0026rdquo;) At the end of the script, as you can see, we start the fastcgi listener, to accept incoming requests.\nNow we are ready to configure the .htaccess file vi .htaccess AddHandler fcgid-script .fcgi RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_URI} !=/favicon.ico RewriteCond %{REQUEST_URI} !^/static/\nRewriteRule ^(.*)$ kermit.fcgi/$1 [QSA,L] Basically we are configuring the Apache RewriteEngine, saying that all requests sould be sent to kermit.fcgi script except requests with /static/ in the url (my static file, like css, imgs, js, \u0026hellip; are there) and favicon.ico.\nThis script should have 755 access rule, so: chmod 0755 kermit.fcgi And that's \"all\". Depending on your hosting and projects could be necessary to install some other packages. On BlueHost to use Django with fastcgi you need to install flup project. $ wget http://www.saddi.com/software/flup/dist/flup-1.0.2.tar.gz $ tar xzvf flup-1.0.2.tar.gz $ cd flup-1.0.2 Like shown before, for Python 2.6/2.7:\n$ python setup.py install --user For previous versions:\n$ python setup.py install --home $HOME/.local Now you can browse your django application :D If you want to see a first result of this test, point your browser on http://kermit.mornati.net\n","date":"1 septembre 2011","externalUrl":null,"permalink":"/install-django-in-shared-host/","section":"Posts","summary":"","title":"Install Django in shared host...","type":"posts"},{"content":"I put these tips here because every time I need to pull a project from GitHub, where I\u0026rsquo;m not a contributor, I\u0026rsquo;ve a certificates problem and I always dismembered the solution :D\n[root@centos564 ~]# git clone https://github.com/onelogin/python-saml.git Cloning into python-saml... error: SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed while accessing https://github.com/onelogin/python-saml.git/info/refs fatal: HTTP request failed The solution, without install of certificate somewhere on the local machine, is:\n[root@centos564 ~]# env GIT_SSL_NO_VERIFY=true git clone https://github.com/onelogin/python-saml.git Cloning into python-saml... remote: Counting objects: 27, done. remote: Compressing objects: 100% (24/24), done. remote: Total 27 (delta 3), reused 25 (delta 1) Unpacking objects: 100% (27/27), done. ","date":"31 août 2011","externalUrl":null,"permalink":"/github-pull-and-certificate-verification-failed/","section":"Posts","summary":"","title":"GitHub pull and certificate verification failed....","type":"posts"},{"content":"I found python 2.6 installed on BlueHost but is not enabled by default. That means if you simply run a python command you will have version 2.4\nuser@mornati.net [~]# python Python 2.4.3 (#1, May 5 2011, 16:39:10) [GCC 4.1.2 20080704 (Red Hat 4.1.2-50)] on linux2 Type \"help\", \"copyright\", \"credits\" or \"license\" for more information. \u0026gt;\u0026gt;\u0026gt; But\u0026hellip; inside the user home folder there is also the version 2.6 (ok, not 2.7 neither 3.0, but at least a step ahead :))\nuser@mornati.net [~]# whereis python python: /bin/python /bin/python.orig /bin/python2.6-config /bin/python2.4 /bin/python2.6 /usr/bin/python /usr/bin/python.orig /usr/bin/python2.6-config /usr/bin/python2.4 /usr/bin/python2.6 /sbin/python /sbin/python.orig /sbin/python2.6-config /sbin/python2.4 /sbin/python2.6 /usr/sbin/python /usr/sbin/python.orig /usr/sbin/python2.6-config /usr/sbin/python2.4 /usr/sbin/python2.6 /lib/python2.4 /lib/python2.3 /lib/python2.6 /usr/lib/python2.4 /usr/lib/python2.3 /usr/lib/python2.6 /usr/include/python2.4 /usr/include/python2.6 /usr/share/man/man1/python.1.gz So /bin/python2.6 is you run command.\nuser@mornati.net [~]# python2.6 Python 2.6 (r26:66714, Apr 1 2009, 20:44:00) [GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2 Type \"help\", \"copyright\", \"credits\" or \"license\" for more information. \u0026gt;\u0026gt;\u0026gt; Now to use by default the version 2.6 without using the \u0026quot;bad\u0026quot; python2.6 command, just edit your .bashrc file. Add this line at the end of your file:\nalias python=”python2.6″ And\u0026hellip;. enjoy!\n","date":"30 août 2011","externalUrl":null,"permalink":"/bluehostcom-and-python-26/","section":"Posts","summary":"","title":"Bluehost.com and Python 2.6...","type":"posts"},{"content":"Lo so, sto diventando troppo iPhone addicted, ma ormai me lo sono preso e (per quanto permesso dalla Apple) cerco di sfruttarlo al massimo.\nHo cercato per qualche tempo in rete per cercare di configurarlo usando OpenVPN, che già uso per connettermi al mio PC di casa; a quanto pare però ci sono due problemi sostanziali: il primo è che non c\u0026rsquo;è il pacchetto SSL per iPhone (almeno non ufficiale), il secondo è che il kernel non permette l\u0026rsquo;apertura della porta tun necessaria per creare una connessione OpenVPN (anche questo sempre nella versione ufficiale dell\u0026rsquo;iPhone).\nMi sono perciò arreso a dovermi cercare un programma alternativo, dopo aver surfato un po\u0026rsquo; mi sono deciso ad installare PPTP, anche se non ufficialmente supportato da Fedora 12 (non esiste il pacchetto nei repository).\nPer l\u0026rsquo;RPM mi sono affidato a qualcun altro che lo aveva già compilato per me:\nhttp://blog.bradiceanu.net/2009/11/20/fedora-12-pptp-server/\nfunziona alla perfezione, quindi ve lo consiglio.\nA questo punto parte la fase di configurazione.\nConfigurazione Server\n/etc/pptpd.conf\nNon serve modificare grandi cose all\u0026rsquo;interno del file. Io ho semplicemente decommentato debug (giusto per tenere traccia di quello che succede. In fase di testing è cosa buona e giusta) e ho aggiunto la configurazione IPs per la mia rete.\nlocalip 192.168.2.11\nremoteip 192.168.2.234-238\nChe indicano semplicemente che il mio server ascolta sulla 192.168.2.11 e che voglio configurare come IP remoti dalla 234 alla 238 (gli ip assegnati ai client remoti per la navigazione all\u0026rsquo;interno della vostra rete)./etc/ppp/chap-secrets\n# client server secret IP addresses\nuser1 pptpd password1 *\nConfigurate le credenziali d\u0026rsquo;accesso per i vostri client. Nel mio caso giusto quello che mi serve per connettermi con l\u0026rsquo;iPhone/etc/ppp/options\nQui ho dovuto aggiungere queste due righe di configurazione\nmru 900\nmtu 900\nperchè il mio iPhone perdeva subito la connessione dal server. In questo modo mi funziona sia usando una WiFi che usando una 3G (e addirittura su EDGE!!)A questo punto la configurazione del PPTP dovrebbe essere completa. Io ho lasciato il default all\u0026rsquo;interno degli altri file di configurazione senza avere nessun tipo di problema.Una cosa da configurare (se lo usate) è iptables per aprire la porta usate dal PPTP al mondo\n#PPTP VPN\n-A INPUT -p 47 -j ACCEPT\n-A INPUT -p tcp \u0026ndash;dport 1723 -j ACCEPT\nQuesto è sufficiente. Abilita il protocollo \u0026ldquo;47\u0026rdquo; (gre) e apre la porta di default di PPTP 1723.La configurazione lato server si chiude qui. Se avete abilitato il debug, come consigliato, potete mettervi in tail su /var/log/messages per visualizzare i tentativi di connessione dai vostri client.\nConfigurazione iPhone\nNel mio caso il solo client interessante per questa VPN è l\u0026rsquo;iPhone. Le configurazione è molto semplice e veloce. Basta andare in Impostazioni -\u0026gt; Generali -\u0026gt; Rete -\u0026gt; VPN e configurare come da figura\nDove in server dovrete inserire l\u0026rsquo;IP pubblico della vostra rete (usate DynDNS o qualcosa di simile se avete IP Dinamico); in account andrà messo il nome utente configurato in precedenza (nell\u0026rsquo;esempio era user1); in password credo sia scontato dire cosa ci vada! :)\nSe volete che tutto il traffico venga rediretto verso la vostra rete domestica lasciate abilitato \u0026ldquo;invia tutto il traffico\u0026rdquo;. Ovviamente facendo in questo modo, se volete navigare dal vostro iPhone quando siete connessi alla VPN dovrete abilitare ipforward sul server per reindirizzare il traffico \u0026ldquo;internet\u0026rdquo; da e verso i vostri client (iPhone nello specifico). Disabilitando l\u0026rsquo;opzione verrà indirizzato verso la VPN solo il traffico specifico; nel mio caso, per esempio, inserendo l\u0026rsquo;indirizzo del Server (192.168.2.11) dove ho un server VNC in funzione (che espongo solo in rete locale) riesco a controllare il mio PC ovunque sono (e ribadisco: anche su 3G).\nTentativo di connessione\nEcco un estratto del log lato server a seguito di un tentativo di connessione dal mio iPhone:\nDec 22 08:24:03 localhost pptpd[12040]: CTRL: Client 95.74.118.140 control connection started\nDec 22 08:24:04 localhost pptpd[12040]: CTRL: Starting call (launching pppd, opening GRE)\nDec 22 08:24:04 localhost pppd[12041]: pppd 2.4.4 started by root, uid 0\nDec 22 08:24:04 localhost pppd[12041]: Using interface ppp0\nDec 22 08:24:04 localhost pppd[12041]: Connect: ppp0 \u0026lt;\u0026ndash;\u0026gt; /dev/pts/2\nDec 22 08:24:09 localhost pppd[12041]: MPPE 128-bit stateless compression enabled\nDec 22 08:24:10 localhost pppd[12041]: found interface eth0 for proxy arp\nDec 22 08:24:10 localhost pppd[12041]: local IP address 192.168.2.11\nDec 22 08:24:10 localhost pppd[12041]: remote IP address 192.168.2.234\nSe tutto è andato a buon fine sul vostro telefono dovestre vedere un \u0026ldquo;inconcina\u0026rdquo; VPN in alto nell\u0026rsquo;area connessioni.\nSe non avete credeto al fatto che il tutto funzionasse anche in EDGE guardate questa immagine! :D Ho provato a controllare il mio server in VPN e non gira nemmeno malissimo, certo non aspettatevi grandissime performance, ma è comunque accettabile!\nConclusioni\nCome ho detto in precedenza, la configurazione che vi ho presentato qui è molto \u0026ldquo;base\u0026rdquo;; non sono stato ad indagare molto sulle varie configurazioni possibili perchè personalmente mi serve giusto per un paio di funzioni la connessione VPN.\nGiusto per non farvi girare a lungo su Google vi mostro come configurare il forwording del traffico internet, nel caso vogliate veicolare tutte le connessioni attraverso la vostra VPN.\n\u0026gt;echo 1 \u0026gt;/proc/sys/net/ipv4/ip_forward\niptables -t nat -A POSTROUTING -o eth0 -s 192.168.2.0/24 -j SNAT \u0026ndash;to 192.168.2.11\nDove, al posto di 192.168.2.11 dovrete mettere l\u0026rsquo;IP del vostro server e configurare di conseguenza gli IP della vostra rete e l\u0026rsquo;interfaccia ethernet/wireless da usare per il forward del traffico internet.\nIl flag all\u0026rsquo;interno di ip_farward non è persistente, quindi se riavviate il PC dovrete riscrivere la riga con \u0026ldquo;echo 1 \u0026hellip;.\u0026rdquo;. Per rendere persistente la modifica basta editare il file /etc/sysctl.conf e modificare la riga\n\u0026gt;# Controls IP packet forwarding\nnet.ipv4.ip_forward = 0\nmettendo un bell'1 al posto dello 0!\n","date":"20 décembre 2009","externalUrl":null,"permalink":"/it/configurare-un-pptp-server-per-iphone/","section":"Posts","summary":"","title":"Configurare un PPTP Server per iPhone","type":"posts"},{"content":"Da buon maniaco degli aggiornamenti, nel passaggio dalla F11 alla F12 ho voluto provare anche a dare una ritoccatina al BIOS del PC, visto che usavo la versione A08 ed era stata rilasciata da un po\u0026rsquo; la A10.\nAndando sul sito di Dell per l\u0026rsquo;aggiornamento, viene ovviamente proposto un simpatico EXE che dovrebbe fare tutto da solo; peccato che su linux il simpatico file EXE non serva assolutamente a niente. Quindi, come già fatto in passato, mi preparo a creare un disco di avvio che mi permetta di aggirare \u0026ldquo;l\u0026rsquo;ostacolo\u0026rdquo; linux e dare nuova vita al mio BIOS. Fortuna vuole, che durante la ricerca dei tool che mi permettessero di creare tale disco di avvio, mi imbatto in tutt\u0026rsquo;altro che mi semplifica la vita: il pacchetto firmware-addon-dell (questo è il nome nei repository fedora, ma immagino che ci sia qualcosa di simile anche per le altre distribuzioni).\nyum install firmware-addon-dell ===================================================== Package Arch Version Repository Size ===================================================== Installing: firmware-addon-dell i686 2.1.2-5.3.fc12 fedora 50 k Installing for dependencies: firmware-tools noarch 2.1.5-2.1.fc12 fedora 141 k libsmbios i686 2.2.16-3.1.fc12 fedora 205 k python-smbios i686 2.2.16-3.1.fc12 fedora 58 k redhat-rpm-config noarch 9.0.3-18.fc12 fedora 53 k smbios-utils i686 2.2.16-3.1.fc12 fedora 13 k smbios-utils-bin i686 2.2.16-3.1.fc12 fedora 38 k smbios-utils-python i686 2.2.16-3.1.fc12 fedora 52 k Questa è la lista di tutto quello che vi verrà installato.Finita l\u0026rsquo;installazione parto in quarta per provare ad eseguire l\u0026rsquo;aggiornamento che mi ero prefisso.\n[root@mmornati ~]# update_firmware Running system inventory... Searching storage directory for available BIOS updates... Checking System BIOS for Latitude D620 - a08 Did not find a newer package to install that meets all installation checks. This system does not appear to have any updates available. No action necessary. La cosa mi lascia ovviamente perplesso! So, dalle informazioni prese dal sito Dell, che esiste il firmware A10, ma questo tool mi dice che non c\u0026rsquo;è niente per il mio PC! Sarà che non funziona niente? Mi leggo il man del tool e scopro che in realtà è necessario agganciare dei repository al programma per fare in modo che possa scaricarsi i firmware corretti.\nwget -q -O - http://linux.dell.com/repo/community/bootstrap.cgi | bashyum -y install $(bootstrap_firmware) Questi sono i comandi da lanciare per \u0026ldquo;installare\u0026rdquo; i repository dell.\n[root@mmornati ~]# wget -q -O - http://linux.dell.com/repo/community/bootstrap.cgi | bash Downloading GPG key: http://linux.dell.com/repo/community/RPM-GPG-KEY-dell Importing key into RPM. Downloading GPG key: http://linux.dell.com/repo/community/RPM-GPG-KEY-libsmbios Importing key into RPM. Downloading GPG key: http://linux.dell.com/repo/community/mirrors.cgi?osname=f12\u0026amp;basearch=i386\u0026amp;redirpath=/repodata/repomd.xml.key Installing dell-firmware-repository-1-4.noarch.rpm Done! Dependencies Resolved ====================================================== Package Arch Version Repository Size ====================================================== Chissà che versione sarà il mio PC?! :D\nA questo punto è sufficiente lanciare il comando di aggiornamento e riavviare il PC per avviare la procedura di aggiornamento vera e propria (il flashing del bios sulla eeprom).\n[root@mmornati ~]# update_firmware --yes Running system inventory... Searching storage directory for available BIOS updates... Checking System BIOS for Latitude D620 - a08 Available: system_bios(ven_0x1028_dev_0x01c2) - a10 Found Update: system_bios(ven_0x1028_dev_0x01c2) - a10 Found firmware which needs to be updated. Running updates... 100% Installing system_bios(ven_0x1028_dev_0x01c2) - a10 Done: Update complete. You must perform a warm reboot for the update to take effect. Assicuro che funziona tutto quanto alla perfezione, infatti ora mi ritrovo con un bel bios A10 sul mio Dell D620!\n","date":"7 décembre 2009","externalUrl":null,"permalink":"/it/aggiornamento-firmware-dell-da-linux/","section":"Posts","summary":"","title":"Aggiornamento Firmware Dell da Linux","type":"posts"},{"content":"A causa di qualche problema avuto nei giorni scorsi con il mio PC (una cancellazione improvvisa e non voluta) ho deciso di installare l\u0026rsquo;ultima versione di Fedora: già che c\u0026rsquo;ero, perchè non far diventare la mia disgrazia un\u0026rsquo;avventura?\nUna delle cose che mi ha lasciato favorevolmente colpito è la fantastica gestione dell\u0026rsquo;iPhone. Sebbene al momento non sia ancora \u0026ldquo;nativa\u0026rdquo; (pare che sarà incluso tutto con la prossima versione di Gnome) basta davvero poco per abilitare tutto.\nyum install gvfs-afc\ne avrete abilitata la gestione del file system dell\u0026rsquo;iphone!\nMaggiori informazioni le potete trovare direttamente sul blog dello sviluppatore della libreria: http://www.hadess.net/2009/10/pushing-patches.html\nCollegato l\u0026rsquo;iPhone alla presa USB del vostro PC vi comparirà, come mostra lo screenshot qui sopra, un\u0026rsquo;icona per l\u0026rsquo;accesso al file System del telefono. In teoria potete copiarci sopra quello che vi pare, al momento però non sono molto sicuro che poi sia tutto visibile dal telefono e dalle applicazioni installate.\nIndagherò meglio nei prossimi giorni! :)\n","date":"21 novembre 2009","externalUrl":null,"permalink":"/it/fedora-12-and-iphone/","section":"Posts","summary":"","title":"Fedora 12 and iPhone","type":"posts"},{"content":"What I want to share with you today is a way to print on a serial printer (in my case I used a barcode printer).\nAnyone is starting reading this post could thik: \u0026ldquo;Yes but is not too difficult to use serial port with Java\u0026rdquo;. Yes but you have to think to another point: we are talking of a web-application and, our printer, is linked to the client (naturally\u0026hellip; maybe my server is not directly accessible). And another interesting point is that our installed JRE could blocked the access to computer ports. I say \u0026ldquo;could\u0026rdquo; but it surely does if you haven\u0026rsquo;t set the \u0026ldquo;policy\u0026rdquo; file or add necessary library to the JRE.\nAnyway\u0026hellip; here you are my experience.\nStarting Point\nThe starting point was locate the necessary libraries, and after some tests with official Sun javax.comm I\u0026rsquo;ve decided to use RXTX library because they sort out with all necessary to use them on all operating systems.\nAnd now\u0026hellip; all is very simple to do.\nApplet\nAs I said before, we need \u0026ldquo;something\u0026rdquo; printed on client machine. So we surely need a simple applet, maybe hidden.\nInside init method you just need to initialize your ports.\n@Override\npublic void init() {\ntry {\nEnumeration portList = CommPortIdentifier.getPortIdentifiers();\nif (portList.hasMoreElements()) {\nthis.portId = ((CommPortIdentifier) portList.nextElement());\n}\n} catch (Exception e) {\ne.printStackTrace();\n}\n//Opening port to test\ntry {\nCommPort serialPort = this.portId.open(\u0026ldquo;SerialPort\u0026rdquo;, 200);\nserialPort.close();\n} catch (Exception ex) {\nex.printStackTrace();\n}\n}\nThe second part of this code Opening port to test is necessary to let your applet always ready to print. During my tests if I didn\u0026rsquo;t try to open the serial port after getting it sometime it does not work.\nAfter this thing, what you need is a simple method to print what you need. I post here my code that, as I said, is used to print a barcode.\npublic void printBarCode(String code) {\ntry {\nCommPort serialPort = this.portId.open(\u0026ldquo;SerialPort\u0026rdquo;, 200);\nPrintStream out = new PrintStream(serialPort.getOutputStream(), true);\nout.println(\u0026ldquo;N\\n\u0026rdquo;);\nout.println(\u0026ldquo;D13\\n\u0026rdquo;);\nout.println(\u0026ldquo;S2\\n\u0026rdquo;);\nout.println(\u0026ldquo;B240,2,0,K,4,5,83,B,\u0026quot;\u0026rdquo; + code + \u0026ldquo;\u0026quot;\\n\u0026rdquo;);\nout.println(\u0026ldquo;P1\\n\u0026rdquo;);\nout.close();\nserialPort.close();\n} catch (Exception ex) {\nex.printStackTrace();\n}\n}\nAll information I\u0026rsquo;m sending to print was getting from EPL print manual. Are just information you need to set-up printer, page and barcode style.\nIf you need anything special you can consider your applet ready to use. You may just package it in a jar and add the jar to your webserver / web-application.\nAdding applet to a page\nInside your page you can just add the applet using applet tag or object tag. In my case I have used applet one because the object give me some problems. In all tests I\u0026rsquo;ve done with applet works correctly both on IE and Firefox with windows and Linux.\n\u0026lt;applet name=\u0026quot;barcodeprinter\u0026quot; id=\u0026quot;barcodeprinter\u0026quot; archive=\u0026quot;/BarCodeApplet.jar\u0026quot; code=\u0026quot;com.bytecode.priterapplet.BarCodePrinter\u0026quot; MAYSCRIPT=\u0026quot;true\u0026quot; style=\u0026quot;width: 1px; height: 1px; background: white;\u0026quot;\u0026gt;\n\u0026lt;/applet\u0026gt;\nNote the mayscript parameter. Is used to let your browser (javascript) to interact with your applet. In fact if you don\u0026rsquo;t add it what you can do is just load a page with an applet that starts printing a \u0026ldquo;static text\u0026rdquo;. Not so useful!\nMore interesting is sure letting the user the selection of the \u0026ldquo;code\u0026rdquo; to print. And you can make this with a very simple javascript code.\n\u0026lt;script type=\u0026quot;text/javascript\u0026quot;\u0026gt;\nfunction inventario(code) {\nvar applet = document.getElementById(\u0026quot;barcodeprinter\u0026quot;);\nif(code != \u0026lsquo;\u0026rsquo;) {\napplet.printBarCode(code);\n}\n}\n\u0026lt;/script\u0026gt;\nAnd to call your javascript\n\u0026lt;a href=\u0026quot;#\u0026quot; onclick=\u0026ldquo;inventario(\u0026lsquo;selectedCode\u0026rsquo;);\u0026quot;\u0026gt;Selected Element\u0026lt;/a\u0026gt;\nConfiguring client\nIf you will try to use your applet without setting your JRE you couldn\u0026rsquo;t print anything. What you need is just get RXTXcomm.jar to copy in JRE_HOME/lib/ext folder, and the correct library (DLL for Windows, .so for linux) and copy it in JRE_HOME/bin folder. For Mac the procedure is a little bit different but you can find the instruction for all operating system directly inside the file you have downloaded from RXTX website.\nEnhancements\nWhat I\u0026rsquo;ve tried after this thing is the dynamic configuration of the client. What you can do is copy the DLL/SO file in a client folder, load dynamically into JRE but, anyway, you have to set, at least, the .policy file on the client. Is a security setting made inside Java: from a webapplet you can\u0026rsquo;t doing what you want on a client computer.\nAnyway here you are a little example of what I\u0026rsquo;ve done\nif (System.getProperty(\u0026quot;os.name\u0026quot;).toUpperCase().contains(\u0026quot;WINDOWS\u0026quot;)) {\nlibraryName = DLL_SERIAL_NAME;\nlibraryFile = System.getProperty(\u0026quot;java.io.tmpdir\u0026quot;) + DLL_SERIAL_NAME;\n} else if (System.getProperty(\u0026quot;os.name\u0026quot;).toUpperCase().contains(\u0026quot;LINUX\u0026quot;)) {\nif (System.getProperty(\u0026quot;os.arch\u0026quot;).contains(\u0026quot;i386\u0026quot;)) {\nlibraryName = SO_SERIAL_NAME_32;\n} else {\nlibraryName = SO_SERIAL_NAME_64;\n}\nchar fileSeparator = System.getProperty(\u0026quot;file.separator\u0026quot;).charAt(0);\nlibraryFile = System.getProperty(\u0026quot;java.io.tmpdir\u0026quot;) + fileSeparator + SO_SERIAL_NAME;\n}\nif (!(verifyLibraryExistence(libraryFile))) {\ncopyResourceFromJar(libraryFile, libraryName);\n}\ntry {\nSystem.load(libraryFile);\n} catch (Exception e) {\ne.printStackTrace();\n}\nDLL and so files are contained in the applet JAR.\nHope to be useful to anyone. And write to me if you have any kind of problem!\n","date":"9 février 2009","externalUrl":null,"permalink":"/javaapplet-used-to-print-over-serial-port/","section":"Posts","summary":"","title":"JavaApplet used to print over Serial Port","type":"posts"},{"content":"I\u0026rsquo;m happy to announce you that Symbolic 1.2.1 is finally sort out!!\nIn this minor release we haven\u0026rsquo;t add any kind of new features, but we worked a lot to make the application stable and stronger.\nThe new features added are listed below:\n- Update graphical icons\n- Reviewed running operations window\n- Solved bugs during script execution\n- Moved engine to use Func 0.23+\n- Added some basic operations\n- Create a symbolic-setup scripts that help you to setup acls on func folders\n- Solved some bugs in VirtualManager execution\n- Create a Makefile to simplify the build process\nTo make a correct installation of the application you can follow this tutorial.\nEnjoy!\nAny kind of comment is welcome!\n","date":"8 janvier 2009","externalUrl":null,"permalink":"/symbolic-v121/","section":"Posts","summary":"","title":"Symbolic v.1.2.1","type":"posts"},{"content":"For the second year, Google has decide to make a Google Developer Day in Milan. For me was the first time at a Google event and I found it really great.\nI arrived at the Hotel got for the event (Nhow Hotel, Via Tortona 35 in Milan) at 9 o\u0026rsquo;clock in the morning, and there was a huge number of person waiting to get into hall. So I start with a little queue (about 15 minutes waiting for solve my registration) and hoping to enter. In fact I didn\u0026rsquo;t make the registration for the event but I was there to substitute a colleague: no one has asked me any king of document that could prove that I\u0026rsquo;m the person I was saying to be! :) So really cool\u0026hellip; for a day I wasn\u0026rsquo;t me!! :)\nAnyway\u0026hellip; after registration where I choose my session (I got all sessions I decided to see\u0026hellip; not bad ;)) I start \u0026ldquo;walking\u0026rdquo; on the hall, where there was a coffee break for a little breakfast, and looking for the programmers present to the event. As I imagined I met some person I knew! (the IT is a little world!! :P)\nAt 10 o\u0026rsquo;clock all people start moving in the \u0026ldquo;light blue room\u0026rdquo; to listen the little introduction from the Italian Marketing Manager (I can\u0026rsquo;t remember the name but\u0026hellip; was a woman!) and something about Google technologies listening Brian Fitzpatrick, a google software engineer. No news listing this presentation: the message send to all developer was \u0026ldquo;Google is the best!\u0026rdquo; ;)\nApp Engine (Kevin Gibbs)\nIntroduction to Google App Engine technology. Basically you can write your application (in different language) and \u0026ldquo;deploy\u0026rdquo; it on Google systems. You have only some limitations in your \u0026ldquo;free account\u0026rdquo; but Google can assure you that all applications are completely scalable: if you need more resources (application on cluster) google gives them to you. What you need to do, is just to write your code. You also have some API that help you to make some operations, for example imaging manipulation, and using this API you have an operation that, if it needs more cpu time, for example, it could has it!\nNot bad!! Naturally the best thing is the possibility to test this technology for free!\nI\u0026rsquo;ve tried to ask something about cluster management (if we can for example manage the http session synchronization) and the answer wans\u0026rsquo;t a \u0026ldquo;NO\u0026rdquo; but something that was walking about \u0026ldquo;NO\u0026rdquo;! :)\nGoogle Data API (Jochen Hartmann)\nThis session was really quiet. A panorama about Google Data API, written for many different languages, that you can use to make in communication your application with Google World! Picasa to get Photos, Google Maps, YouTube and all other things.\nI\u0026rsquo;ve no idea on how I could use all these things\u0026hellip; but now I know what can I do and how! :P\nGeo (Nicola Ferioli)\nWow an Italian Talk!! I was exited just before the talk because, maybe (or surely) for the presence of an italian speaker, during exposition there were many questions (so many interruption of the talk) and at the end of the talk I didn\u0026rsquo;t have time to talk with Nicola about geo. I hope I\u0026rsquo;ll have time to talk with him in the future.\nAll of us have tried to use Google Earth and surely Google Maps, here the presentation was about the integration of these tools inside other Web Application. What you can do is really impressive. You can simply insert a map in your page and then add to this \u0026ldquo;image\u0026rdquo; a second layer with some markers to shown some point-of-interest on your map. Naturally generated map is the same you can use on google web site, so you can navigate it and change details and zoom levels.\nIf you \u0026ldquo;don\u0026rsquo;t like\u0026rdquo; (or better you don\u0026rsquo;t need) the Google Map, you can also change the \u0026ldquo;skin\u0026rdquo; you get from google with your own pictures. So what you get from google is only the engine to navigate your image and add over it some markers.\nSame things could do using the newest Earth plugin: you can insert in you web page an \u0026ldquo;image\u0026rdquo; get from Earth and navigate it!\nIntro to Android\nAndroid was the most impressive talk I\u0026rsquo;ve seen to GDD. The real \u0026ldquo;presentation\u0026rdquo; of operating system was just about the main features offered by Android, what has gotten my attention was the \u0026ldquo;demo application\u0026rdquo; that speaker (unfortunately I can\u0026rsquo;t remember the name) has realized using eclipse and android plugin. In 30 minutes, showing also all the features offered by eclipse android plugin, he has created a simple Java application that can read contacts from phone contacts list and show results to the user.\nFor a Java programmer is really simple to create a new application for the phone and, in my opinion, this will be the cool feature to get the market and move many users to this new phone (both Symbian users and iPhone users ;)).\nYesterday (22 Oct 2008) the first Google Phone (with HTC hardware) is sorted out in the US\u0026hellip; We have just to wait to buy phone also here in europe! :)\nWas a really cool events and I have to thanks my colleague Luca for the opportunity! ;)\n","date":"22 octobre 2008","externalUrl":null,"permalink":"/google-developer-day-2008-milan/","section":"Posts","summary":"","title":"Google Developer Day 2008 - Milan","type":"posts"},{"content":"Probabilmente quanto sto per dirvi è condizionato dal mio modo di pensare e vedere le cose: occupandomi di OpenSource (sia passivamente che attivamente come programmatore su diversi progetti) non posso che difendere questo modo di produrre software.\nPrima di tutto c\u0026rsquo;è da fare un po\u0026rsquo; di chiarezza su cosa viene considerato OpenSource. La credenza popolare è che un applicativo OpenSource è qualcosa di gratuito e, per questo motivo, non possa competere con programmi concorrenti che vengono pagati milioni di euro (per fare le stesse cose).\nNon metto in dubbio che parte di questa visione sia corretta (i programmi OpenSource, rilasciati sotto qualsiasi tipo di licenza previsto, sono gratuiti), ma per il resto non posso che restare inorridito ogni volta che sento pronunciare le suddette parole.\nI proprietari, gli amministratori delegati, i project manager, o qualsiasi altra figura che debba prendere decisioni nell\u0026rsquo;ambito dell\u0026rsquo;IT, si sente molto meglio se può andare a dire in giro che per il loro server di posta, per esempio, spende ogni anno un cetomila euro (nella migliore delle ipotesi).\nIo fossi in loro mi sentirei male sapendo che sto buttando così tanti soldi per un solo server di posta, ma come ho premesso all\u0026rsquo;inizio io in questo caso conto poco, perchè di parte.\nL\u0026rsquo;OpenSource è innanzitutto comunità: i programmi vengono sviluppati da centinua di developers, che molto spesso contribuiscono per passione nel loro tempo libero, ma comunque supervisionati da una serie di responsabili di progetto. Quindi, quando le persone che ci lavorano sono centinaia o migliaia (a seconda del progetto preso in considerazione) dislocate negli angoli più remoti della terra (quindi su un diverso fuso orario), quanto pensate ci possa volere a risolvere un problema che la “tal società” possa riscontrare durante l\u0026rsquo;uso del software? E quanto invece può metterci la società che pagate centinaia di milioni?\nNon possiamo dire, per esempio, che un database MySQL (opensource) sia sempre meglio di un database Oracle (closed source), anzi in alcuni casi MySQL proprio non riesce a raggiungere le presetazioni offerte da Oracle. Quello che è sicuro è che per il 90% degli utilizzatori attuali di Oracle basterebbe un\u0026rsquo;istanza di MySQL per fare le stesse identiche cose senza metter mano al portafogli.\nNel panorama aziendale italiano c\u0026rsquo;è molta diffidenza verso qualsiasi cosa che sia OpenSource. Il problema è che il tutto è viziato da una classe dirigente ormai un po\u0026rsquo; in la con gli anni: tutti si affidano ancora alle società che da anni (da quando tal dirigente ha cominciato la sua attività lavorativa) sono leader nell\u0026rsquo;IT proponendo i prodotti più disparati. Ma non sono comunque convinto, considerando anche le comunicazioni che riesco a cogliore ogni tanto passando nella metropolitana di Milano, che andando a svecchiare la fascia dei Decision Maker delle aziende si possano fare passi avanti in questo senso: anche molti giovani hanno la stessa idea.\nBisogna solo convincere e convincersi che pagare milioni di euro può non essere la soluzione migliore per le proprie esigenze. Un programma OpenSource permette di essere provato senza impegni andando (eventulamente) a pagare il solo supporto che garantisce di avere una persona da poter chiamare in caso di problemi.\nFortunatamente non tutti la pensano allo stesso modo\u0026hellip; i programmatori che dedicano il loro tempo libero partecipando a qualche progetto OpenSource, sicuramente trovarenno le lore lines-of-code installate da qualche parte nel mondo. E\u0026rsquo; comunque una soddisfazione (minima)\u0026hellip; ma possiamo fare di meglio!!\n","date":"31 juillet 2008","externalUrl":null,"permalink":"/it/opensource-italia-come-sempre-fanalino-di-coda/","section":"Posts","summary":"","title":"OpenSource: Italia come sempre fanalino di coda","type":"posts"},{"content":"L\u0026rsquo;interesse del mondo dell\u0026rsquo;Information Techonology si sta spostando verso tool di gestione delle infrastrutture. La rivoluzione che si sta avendo in questi ultimi anni, con un movimento esponenziale in questo periodo, è che, con la produzione di macchine sempre più potenti, si riesce a ridurre il numero di quelle fisiche (lo scatolotto che vi ritrovate in mano anche voi a casa) incrementando invece quelle virtuali (gestite da tool come virtualbox per esempio).\nUn indizio di ciò lo si è avuto dall\u0026rsquo;orientamento che hanno preso eventi recenti, quali per esempio il Red-Hat Summit 2008 o la FUDCon di Boston, dove tutti i talker hanno presentato un programma o progetto che fosse orientato al Management delle infrastrutture.\nSe spostate l\u0026rsquo;attenzione dal vostro pc di casa e pensate ad una grossa azienda chi si ritrova con un centinaio di macchine, tra fisiche e virtuali, capite come sia indispensabile avere un buon metodo di gestione. Ed è qui che entrano in gioco tool di nuova concezione, molti prodotti direttamente da Red-Hat, e dei quali fa parte anche Symbolic. Symbolic è una web-application (è limitativo chiamare “sito web” applicazioni di tale portata, ma concordo con voi sul fatto che l\u0026rsquo;utente finale vi ci accede come se fosse un banale sito web) che si prefigge come scopo quello di facilitare la gestione di un elevato numero di macchine centralizzandone il controllo.\nIl core dell\u0026rsquo;applicativo è basato sul Fedora Unified Network Controller (per gl\ni amici Func) che garantisce cominicazione sicura\n(certificata con SSL) fra le varie macchine del vostro Network, permettendo di essere controllate attravero un unico server, che nel gergo Func si chiama Certmaster.\nQuando dico che una macchina può essere “controllata”, intendo che lanciando un comando dal certmaster e definendo come target la particolare macchina che volete gestire, potrete fare tutte le operazioni come se foste fisicamente sulla tastiera collegata a quel pc (o per meglio dire, essendo il tutto orientato a linux, come si vi collegaste via SSH lanciando i comandi richiesti).\nVisto che spiegare a tutti quali sono i comandi, come si usano, come si chiamano le varie macchine, ecc., può diventare difficile e noioso, ecco pronta l\u0026rsquo;interfaccia grafica che, attraverso il classico point-and-click, permette di fare le stesse identiche cose.\nPer ingolosirvi un po\u0026rsquo; vi cito (solo) alcune delle principali funzionalità che l\u0026rsquo;attuale versione di Symbolic mette a disposizione.\nAccesso sicuro garantito dai prinpili meccanismi di autenticazione, con possibilità di collegarlo al vostro server LDAP/Kerberos/ActiveDirectory\nAbilitazione di operazioni e macchine diversificato per utente o gruppi di utenti\nXML-RPC Server: garantisce la possibilità di comunicare con Symbolic anche da tool e script esterni\nVirtual Machine Manager: gestore grafico delle macchine virtuali. Con possibilità di verificarne lo stato ed eseguire le funzioni di start, stop, ecc.\nPlugin: l\u0026rsquo;applicazione può essere estesa attraverso l\u0026rsquo;installazione (anche a caldo) di plugin esterni\nFunzionalità Dinamiche: è possibile istruire l\u0026rsquo;applicazione (configurando il tutto via web) affinchè in talune circostanze i controlli si rilassino un po\u0026rsquo;. Per esempio se un vostro PC risulta crashato, e symbolic se ne accorge, può abilitare per tutti gli utenti il comando di restart di quella macchina.\nVi sembra abbastanza per cominciare a dare un\u0026rsquo;occhiata a quest\u0026rsquo;applicazione?\n","date":"30 juillet 2008","externalUrl":null,"permalink":"/it/symbolic-il-futuro-della-net-administration/","section":"Posts","summary":"","title":"Symbolic – Il futuro della Net-Administration","type":"posts"},{"content":"To prevent a possible jars conflict during dynamic plugin execution (what I was talking about in my previous post), I found a simple solution that I\u0026rsquo;m pasting for you here:\nclass DefaultPluginJob { static triggers = { } static cachedClassLoader = [:] def execute(context) { def libraryFolder = context.mergedJobDataMap.get(\"libraryFolder\") if (!cachedClassLoader[context.mergedJobDataMap.get(\"jobName\")]) { log.debug \"Constructing class loader for ${context.mergedJobDataMap.get(\"jobName\")}\" def urls = [] libraryFolder?.eachFile { library -\u0026gt; log.debug \"Adding file ${library}\" urls.add(library.toURL()) } cachedClassLoader[context.mergedJobDataMap.get(\"jobName\")] = new URLClassLoader(urls as URL[], this.class.classLoader) } def file = context.mergedJobDataMap.get(\"scriptFile\") Binding binding = new Binding(context.mergedJobDataMap); GroovyShell shell = new GroovyShell(cachedClassLoader[context.mergedJobDataMap.get(\"jobName\")], binding); def scriptResult = shell.evaluate(file.text); } } A new URLClassLoader will be created for each script that need to be run from my job, and it will contain a copy of parent class loader and new jars needed for the script execution.\nWhat you can find here is the current solution I\u0026rsquo;m using in my application: all classloaders are cached in a static map (thanks again to Sergey for suggestion ;)) and passed to GroovyShell for script exection. Using cache for classloader granted that I\u0026rsquo;ve only one classloader generation for each script (and not one for each job execution). You loose memory to get perfermance! ;)\nWaiting for comments about this way to run a java web application! :)\n","date":"29 juillet 2008","externalUrl":null,"permalink":"/grails-dynamic-plugins-isolated-classloader/","section":"Posts","summary":"","title":"Grails - Dynamic Plugins - Isolated Classloader","type":"posts"},{"content":"The new functionality offers from Groovy/Grails make you able to write a very dynamical application and the possibility to add some new features to your web application at run-time!! Naturally we are exiting a \u0026ldquo;little bit\u0026rdquo; from JEE standards where our library/scritps/class and so on, must be in your war or provided from application server.\nWhat I think about this, is that this way to see the development is a little bit old and could bring java to death! I\u0026rsquo;m dramatic, I know, but there is lot of confusion inside java, java standard and all java project.\nIt\u0026rsquo;s time to change!! :)\nAnyway\u0026hellip;\nWhat I\u0026rsquo;ll explain is the way that we are using to add plugins (some functions) to OpenSymbolic, after application installation and, if you want, after webserver startup (I don\u0026rsquo;t know what JBoss thinks about this\u0026hellip; I\u0026rsquo;ll do some test when all will be ready).\nOur goal was to obtain a way to add some schedulable functions (Quartz Job) to our application leaving to user the decision about which he needs and which not (in a future article we will see the real usage in Symbolic).\nFirst step\nQuestion: How can I add a Job Dynamically to my scheduler?\nAnswer: at the moment I can\u0026rsquo;t! :(\nSolution: I called Sergey Nebolsin, the Quartz plugin developer, exposing my problem, and in ONE NIGHT (underline well ONE), he send me the implementation.\nLesson learned: If I need something for the following day\u0026hellip; I will have to call Sergey!! :P (Really thanks again for your work and help Sergey!!)\nSecond step\nWriting a bit of code to see if I can really do what I\u0026rsquo;m thinking.\nHere what I have.\nAll my plugins will be contained in a specific folder on my machine (configured in configuration file of my application).\n/etc/symbolic/plugins\n-\u0026gt; /nagios_plugin\n-\u0026gt; /msn_plugin\n-\u0026gt; /dont_know_plugin\nEach of these folders contains the file I need: in my test a configuration file and one script file.\nSo\u0026hellip; with a simple script I could try to scan these folders look for what I need.\nimport org.codehaus.groovy.grails.commons.ConfigurationHolder\nclass PluginService {\nboolean transactional = false\nstatic CONFIG_FILE_EXT = \u0026lsquo;conf\u0026rsquo;\nstatic SCRIPT_FILE_EXT = \u0026lsquo;groovy\u0026rsquo;\nstatic LIB_FOLDER = \u0026rsquo;lib\u0026rsquo;\npublic void init() {\n//Scan Plugins Folder\ndef pluginsFolder = ConfigurationHolder.config.plugin.folder\nlog.debug \u0026ldquo;Scanning plugin folder: ${pluginsFolder}\u0026quot;\nif (pluginsFolder) {\nnew File(pluginsFolder).eachDir {dir -\u0026gt;\nlog.debug \u0026ldquo;Directory found: ${dir}\u0026quot;\ndef dataMap = [:]\n//Read Plugin File and configuring it\ndir.eachFile {file -\u0026gt;\nif (file.isFile()) {\nif (file.name.contains(CONFIG_FILE_EXT)) {\nlog.debug \u0026ldquo;File ${file.name} is the configuration file\u0026rdquo;\ndef pluginConfiguration = readConfigFile(file)\ndataMap[\u0026lsquo;pluginName\u0026rsquo;] = pluginConfiguration.get(\u0026ldquo;job.name\u0026rdquo;)\ndataMap[\u0026lsquo;cronString\u0026rsquo;] = pluginConfiguration.get(\u0026ldquo;job.cron\u0026rdquo;)\n} else if (file.name.contains(SCRIPT_FILE_EXT)) {\nlog.debug \u0026ldquo;File ${file.name} is the script file\u0026rdquo;\ndataMap[\u0026lsquo;scriptFile\u0026rsquo;] = file\n} else {\nlog.debug \u0026ldquo;File ${file.name} will be ignored!\u0026quot;\n}\n} else {\nif (file.name.equals(LIB_FOLDER)) {\nlog.debug \u0026ldquo;Lib folder found\u0026hellip; adding jars to classpath.\u0026quot;\n}\n}\n}\nDefaultPluginJob.schedule(dataMap[\u0026lsquo;cronString\u0026rsquo;], dataMap)\n}\n}\nelse {\nlogger.info \u0026ldquo;No plugins folder set. Nothing to load!\u0026quot;\n}\n}\ndef readConfigFile = {file -\u0026gt;\nProperties prop = new Properties()\nif (file) {\nprop.load (new FileInputStream(file))\n}\nprop\n}\n}\nIt\u0026rsquo;s just a simple test\u0026hellip; there are many improvements to do! ;)\nClass \u0026ldquo;DefaultPluginJob\u0026rdquo; is a simple Quartz Job, that you can create in a standard grails way, and, with a new plugin release made by Sergey, it has some static methods that you can use to add your job to your quartz scheduler!\nHere is the code of Job:\nimport org.quartz.JobDataMap\nimport org.quartz.JobExecutionContext\nclass DefaultPluginJob {\nstatic triggers = { }\ndef execute(context) {\nString instName = context.getJobDetail().getName();\nString instGroup = context.getJobDetail().getGroup();\ndef file = context.mergedJobDataMap.get(\u0026ldquo;scriptFile\u0026rdquo;)\nBinding binding = new Binding();\nGroovyShell shell = new GroovyShell(binding);\ndef scriptResult = shell.evaluate(file.text);\n}\n}\nIt seems very simple, no? :)\nThird step\nTo create a real extension of your application, you may need to add also some libraries used by your script: you cannot add all existing java libraries to your application because someone would create a plugin that will use that libraries! ;)\nA very simple way we have found to solve this problem is adding a lib sub-folder to your plugin folder, where you can put all your libraries.\nThe plugin server, scanning folders, will add your jars to root class loader in this way:\nthis.class.classLoader.rootLoader.addURL(new URL(\u0026quot;${file}\u0026rdquo;))\nBy now you can use all classes contained in your added jar files! :D\nFuture\u0026hellip;\nWhat I need to solve now, is a way to prevent jar conflict. Add all to root class loader, in fact, could make some problems to your application or simply to your other plugins.\nI think that a solution could be write something inside your job, that will add your library only to your job instances during (before) execution of the script!\nIf anyone has any ideas about that\u0026hellip; is welcome! :P\n","date":"25 juillet 2008","externalUrl":null,"permalink":"/grails-dynamic-plugins-for-your-applications/","section":"Posts","summary":"","title":"Grails - Dynamic Plugins for your applications","type":"posts"},{"content":"Symbolic engine, or OperationRunner, is something like what we have seen for the scripts: a couple of job/thread that going in polling over database, look for ready operation, or completed (success/failed) operations.\nAll this operation will be completely asynchronous both for user and symbolic application: a ControllerJob will call each \u0026ldquo;n\u0026rdquo; seconds to verify the state of ran operation.\nSelect machine(s), decide which operation he want, and complete (if required) the parameters needed for the executionAn entry is stored in database and set in \u0026ldquo;ready state\u0026rdquo;RunnerJob (is not the same job used to run scripts!) check, each \u0026ldquo;x\u0026rdquo; seconds, if there\u0026rsquo;s any operation to run.When it finds something, using func api, he call func and change the operation status in database: running stateFunc, through func-transmit script, will call required minion creating an \u0026ldquo;async job\u0026rdquo; (the job id will be returned to RunnerJob and stored in database)Another job, the ControllerJob, verifies if there is any running operation (contacting database) and, using stored job id, it asks to Func the state of that jobWhen it receives a \u0026ldquo;complete job\u0026rdquo; response (could be success or failed) store the information in database setting also the status to correct value (success/error)User can see the result for the operation he called.\n","date":"23 juillet 2008","externalUrl":null,"permalink":"/symbolic-operations-runner/","section":"Posts","summary":"","title":"Symbolic - Operations Runner","type":"posts"},{"content":"As official guide illustrates, symbolic administrator can create function users could do, simply adding new operations. An operation is an object built using func module, method and parameters.\nWhat administrator has to do to add a new operation is:\nAssign a name that will be shown to usersSelect a func operation, selecting module and method over that module. i.e. module: command, method: runAdd some optional parameters to complete the operation. For example if he has coded the operation over module command with method run, he need to decide which linux command function he want to execute.Add some optional parameters that will be ask to user before operation run. To make completely dynamic the operation coding, in fact, something need to be added during the execution by the user. For example, administrator could decide that for a specific group of users, must be created an operation to make able the execution of all linux command. So he has to code an operation over \u0026ldquo;command run\u0026rdquo; and the add a parameter named \u0026ldquo;Command to run\u0026rdquo; that users will complete before operation running.\nAuthority assignment is the last operation he has to do to shown new operation to the user. In fact, all symbolic operations, machines and scripts are shown only to users that are enabled to use.\nAt runtime this operation will be completed with machine hostname (the one where user click before operation selection), the (optional) parameters that user must complete and all is stored in a table with \u0026ldquo;ready state\u0026rdquo; attached. Will be symbolic engine to select ready operation and call func for the execution.\n","date":"23 juillet 2008","externalUrl":null,"permalink":"/what-are-symbolic-operations/","section":"Posts","summary":"","title":"What are symbolic operations?","type":"posts"},{"content":"Most of the processes, script or operation, you can run using Symbolic are asynchronous, this means that multiprocess/thread control is delegated to func. There are some other situation where you may need run process in synchronous mode: some admin procedures should be run directly waiting for the response (i.e is needed to going on or to complete symbolic configuration).\nFollowing the standard \u0026ldquo;running channel\u0026rdquo; used to run async script, can generate a bottleneck: my RunnerJob must wait the end of ran process and so it\u0026rsquo;s impossible to run any other operation till the end of the async one.\nTo solve this problem Symbolic has a Pool Manager used and called from RunnerJob. Is a bit more than simple synchronized list of processes: you can decide how many \u0026ldquo;concurrent\u0026rdquo; synchronous processes you want and it exposes some method to \u0026ldquo;book\u0026rdquo; your process execution, send process and check process status.\nFollowing diagram exposes how Symbolic manages synch operations.\nUser or administrator \u0026ldquo;try\u0026rdquo; to run a synch operationUser calls reach the RunnerJob that, calling PoolManager try to book a place for sync job executionIf there is a place to process user request, PoolManager responds with the position assigned in pool list. If you want this is what happens when you want to book an hotel room or a theater seat: you call, if there\u0026rsquo;s what you ask you received booking number, in any other case you have to wait.\nIf JobRunner receives a useful position in the pool list, he start the process asked from the user and then send reference to running process and provided position to PoolManager. If JobRunner receives a \u0026ldquo;no free places found\u0026rdquo; it returns an answer the user with an error message.Process is queued in the pool list and it goes ahead with the executionAny \u0026ldquo;X\u0026rdquo; seconds there is another job, ControllerJob, that calls PoolManager asking if there is any process that has finished the executionIf something is found a messege ControllerJob sends a message to User with the execution result.This kind of implementation is something like asynchronous call for the Symbolic engine because there is no \u0026ldquo;internal\u0026rdquo; process that wait for the execution end. It\u0026rsquo;s only an synch call for the user that can do nothing more on the application till the end of the process he ran.\n","date":"22 juillet 2008","externalUrl":null,"permalink":"/symbolic-synch-operation-pool/","section":"Posts","summary":"","title":"Symbolic: Synch Operation Pool","type":"posts"},{"content":"It\u0026rsquo;s seems one of the many posts you can find in internet that helps you to generate a tree-menu using javascript/java/\u0026hellip; What I want to illustrate is a method that use recursion in back-end (java code that generate the Tree structure) and front-end (a gsp that shown the tree calling itself!!).\nIn fact, the start point, is that our page is not a \u0026ldquo;simple\u0026rdquo; page but is written as grails templates.\nTemplate is a grails way to get your front-end code structured and provide an highly re-usable mechanism that you can call simply using a defined taglib.\nclass TreeMenu {\ndef addNode = {nodeElement, machine, tagList -\u0026gt;\ndef nodes = [:]\nnodes[machine.hostName] = machine\ndef newList = tagList - nodeElement\nnewList?.each {currentTag -\u0026gt;\nnodes[currentTag.name] = addNode(currentTag, machine, newList)\n}\nnodes\n}\n}\nIs an extraction of my program codes\u0026hellip; in the original version the data structure is not a simple Map but I\u0026rsquo;ve a complex object, so I can do, for example, a check if there is a node with current provided name and so on.\nWhat this code try to do, is to add a Machine to each Tree-Tag I\u0026rsquo;m sending to function as a List.\n\u0026ldquo;nodeName\u0026rdquo; is the current node where I want my machine\n\u0026ldquo;machine\u0026rdquo; is the object name I want in my tree\n\u0026ldquo;tagList\u0026rdquo; is the list of all tree-node where my machine will be put\nFor example, I could have this situation\nmachine: \u0026ldquo;TryMachine\u0026rdquo;\ntagList: [\u0026ldquo;A\u0026rdquo;, \u0026ldquo;B\u0026rdquo;]\nTreeMenu.addNode(\u0026quot;Root\u0026quot;, machine, tagList)\nThe result of this method invokation will be:\nRoot |-\u0026gt; A\n| |-\u0026gt;B\n| | |TryMachine\n| |TryMachine\n|-\u0026gt; B\n| |TryMachine\n| |-\u0026gt; A\n| | |TryMachine\n| TryMachine\nDisplay the tree\nThe extraordinary feature offers by grails is, as I said, the usage of recursion on the front-end, that make you able to create a page without insertion of some java codes: all just with default grails taglibs.\nHere an example gets from my code:\n\u0026lt;g:each in=\u0026quot;${nodes}\u0026quot; var=\u0026quot;element\u0026quot;\u0026gt;\n\u0026lt;g:if test=\u0026quot;${element.value instanceof Machine}\u0026quot;\u0026gt;\n${element.name}\n\u0026lt;/g:if\u0026gt;\n\u0026lt;g:else\u0026gt;\n\u0026lt;g:machineList template=\u0026quot;/templates/machineTree\u0026quot; data=\u0026quot;${element}\u0026quot;/\u0026gt;\n\u0026lt;/g:else\u0026gt;\n\u0026lt;/g:each\u0026gt;\nAnd in your page, where you want to put your tree, you can just simply call the template:\n\u0026lt;g:machineList template=\u0026quot;/templates/machineTree\u0026quot; data=\u0026quot;${treeData}\u0026quot;/\u0026gt;\nIs a just a simple example (and, in fact, I\u0026rsquo;m not sure that with mods I\u0026rsquo;ve done to create this post, all work well :P), If you want you can make some improvements to this code, attaching, for example, javascript functions to get your tree-node opened and closed, or some other kinds of object type.u\n","date":"21 juillet 2008","externalUrl":null,"permalink":"/generate-a-simple-tree-menu-in-grails/","section":"Posts","summary":"","title":"Generate a simple tree-menu in Grails","type":"posts"},{"content":"In this article we will see how Symbolic runs a pre-configured user script in asynchronous mode.\nWhy we say pre-configured?\nIn Symbolic application, the administrator can \u0026ldquo;upload\u0026rdquo; on the server a specific \u0026ldquo;well-formed\u0026rdquo; script, written in Pyhton, Groovy, Bash or Perl. After this procedure Symbolic recognizes the list of installed scripts and proposes them to enable users. (As we have seen in a previous article)\nThis schema illustrates the main steps follow to execute script and propose result to the user.\nFirst of all, a logged user has to select a script from the listThis selection produces an entry in Symbolic Database with script information and state READY (like in a MicroProcessor state ready means that operation is waiting the execution).A \u0026ldquo;runner-process\u0026rdquo; (Quartz Job for Java Developers), launched at the application start-up, queries database in polling looking for new ready-state scripts. By default it is configured to wake up each 5 seconds, but the administrator could decide to change this polling time with a simple modification to Symbolic configuration fileWhen the \u0026ldquo;runner-process\u0026rdquo; find a new ready-state script, runs it using a new \u0026ldquo;System\u0026rdquo; process (a process external to Symbolic application) and set the state to RUNNING. Whereas the script is to run in async mode, the runner-process job is finished.The script can communicate with Symbolic application through a provided Xml-RPC Server (embedded in Symbolic and reachable at \u0026ldquo;symbolic/api/xmlrpc\u0026rdquo; address).\nThe access to the server xml-rpc is protected with username and password. This provides an other step in Symbolic security, making only well-formed script able to communicate with Symbolic application.\nBy default a user \u0026ldquo;externalscript\u0026rdquo; is created during the Symbolic installation (with default password externalscript); the administrator could change this credentials so only \u0026ldquo;really-certified\u0026rdquo; scripts could communicate with that Symbolic instance.\nAt the moment Xml-Rpc server exposes only two methods: getAllMachines and postInformation. The first one is used to get list of Symbolic certified and controlled machines; the second one is used to communicate the script execution result to Symbolic.When postInformation is invoked, xml-rpc server get the result and saved the correct status in the database. Each ran script knows the database ID that must be posted with the result information. This is the only way that make symbolic able to recognized information posted.If user try to get ran script status in this moment, it find script result information (with associated error or success result).\n","date":"21 juillet 2008","externalUrl":null,"permalink":"/symbolic-scripts-runner/","section":"Posts","summary":"","title":"Symbolic - Scripts Runner","type":"posts"},{"content":"The first step to make a well-formed script is to add, at the beginning of the script file in a \u0026ldquo;commented area\u0026rdquo;, a series of tag that will recognize from Symbolic and adapt the application accordingly to the script.\nTo write your own script you can choose between one of the four supported languages: Groovy, Python, Bash, Perl\nThe current accepted tag are:\n@Name: the name for your script. Is used to shown to the users your saved script.@Author: name and references of the script\u0026rsquo;s author.@Type: type of your script. Valid values are: python, groovy, bash, perl@Description: a full description to inform users about what script really does.Putting just this four simple tags at the beginning of your script, and copying your script in symbolic scripts folder, make Symbolic application able to recognized the script so that users can run it.\nXML-RPC Communication\nSymbolic exposes a service to which script can connect to get some useful information (like a Symbolic certified machines) and to post execution result.\nAll \u0026ldquo;user-runnable\u0026rdquo; scripts in fact are launched in a way that can we call \u0026ldquo;asynchronous\u0026rdquo;: Symbolic does not wait the answer from each ran script; so the only way that we can use to communicate the result to Symbolic if calling it through a defined service.\nIn Symbolic there is an implementation of XML-RPC server that exposes a method to post result from script:postInformation(result). So in your scripts you have to put some lines of code that call XML-RPC server to post the result information.\nWhen Symbolic call a script provides these parameters:\n-a: asynchronous execution. Caller will not wait for script answer, so result must be posted through xml-rpc server\n-p proccesID: is the Symbolic identification of ran script\n-s serverAddress: xml-rpc server address.\nFor example if you have a python script, symbolic will call it using something like:\npython script.py -a -p 10 -s http://localhost:8080/symbolic/api/xmlrpc\nSo you need to get this parameters inside your script if you want to communicate with Symbolic.\nThe answer that Symbolic expects must be formatted as dictionary/map with these information:\n[\u0026ldquo;process_id\u0026rdquo;:SYMBOLIC_PROC_ID,\u0026ldquo;status\u0026rdquo;:process_status,\u0026ldquo;response\u0026rdquo;:some_information]\nprocess_id: is the process id provided during symbolic script invocation.\nstatus: is the result of your script/process (0:Success, 1:Error)\nresponse: what you want. It\u0026rsquo;s better something Human-Readable because will be shown to user without any kind of parsing.\nAuthentication\nThe symbolic rpm server is secured behind a password protection. So when you create, inside your script, an instance of xmlrpc client you need to post BasicAuthentication username and password or you will get an \u0026ldquo;access forbidden error\u0026rdquo; from Symbolic.\nAs default setting there is an user that scripts can use to connect to server:\nusername: externalscript\npassword: externalscript\nSymbolic administrator could change this information or create many other script-enable accounts. These account must have associated a custom script authority (like default created during installation) or root authority (it\u0026rsquo;s better to do not use this authority to make script enable to communicate with symbolic!)\n","date":"21 juillet 2008","externalUrl":null,"permalink":"/well-formed-symbolic-script/","section":"Posts","summary":"","title":"Well-Formed Symbolic Script","type":"posts"},{"content":"Qualche mese fa (ammetto di essere un po\u0026rsquo; in ritardo con questo post) in un\u0026rsquo;intervista durante una conferenza all\u0026rsquo;Institute for Systems Biology di Seattle, lo zio d\u0026rsquo;america, meglio conosciuto come Sig. Cancelli fa delle affermazioni abbastanza discutibili sull\u0026rsquo;opensource e sulla licenza GPL in particolare.\nLa domanda chiedeva un parare sulla possibilità di Microsoft di adottare software opensource nelle ricerche in campo medico/farmacologico (in inglese rende meglio health research)\u0026hellip; lo zio Billy risponde così:\nThere\u0026rsquo;s free software and then there’s open source,\u0026quot; he suggested, noting that Microsoft gives away its software in developing countries. With open source software, on the other hand, \u0026ldquo;there is this thing called the GPL, which we disagree with.\u0026rdquo; Open source, he said, creates a license \u0026ldquo;so that nobody can ever improve the software,\u0026rdquo; he claimed, bemoaning the squandered opportunity for jobs and business. He went back to the analogy of pharmaceuticals: \u0026ldquo;I think if you invent drugs, you should be able to charge for them,\u0026rdquo; he said, adding with a shrug: \u0026ldquo;That may seem radical.\nLa sola cosa che mi sento di dire è che il vecchio DOS, ciò su cui poi Microsoft ha fondato tutta la sua fortuna (nessuno può negare che Bill Gates abbia avuto in quegli anni un grande fiuto per gli affari e per la direzione da seguire in campo informatico) sebbene non fosse opensource si potesse tranquillamente considerare gratuito. Ricordo ancora quando, comprando il mio primo PC (un fantastico 386 DX 40 MHz) il gestore del negozio mi chiese se volessi pagare la licenza di MS DOS\u0026hellip; io ovviamente, ignaro ancora dell\u0026rsquo;andazzo in campo informatico, chiesi se non era così che si doveva fare\u0026hellip; lui tranquillamente rispose con un \u0026ldquo;tanto se lo copiano tutti\u0026rdquo;.\nUna società la cui fortuna è basata sul \u0026ldquo;copia e incolla\u0026rdquo; di tutti i suoi prodotti, non può criticare l\u0026rsquo;OpenSource. La GPL in fondo è solo una lincenza con cui è possibile rilasciare il proprio codice (vedi il Kernel di Linux)\u0026hellip; ma non si può dire che questo possa in qualche modo impedire un\u0026rsquo;evoluzione del codice stesso.\nMicrosoft è \u0026ldquo;costretta\u0026rdquo; a pagare centinaia di programmatori, per far uscire aggiornamenti per la risoluzione dei numerosi bachi che gli stessi programmatori hanno inserito\u0026hellip; la comunità OpenSource permette a chi ha scoperto il baco di scrivere la patch che lo risolve, in modo che a distanza di poche ore tutti possano giovare dell\u0026rsquo;aggiornamento.\nIn altri post su internet mi è capitato anche di leggere un commento, sempre dello Zio, in cui affermava che Linux era destinato al declino, come qualche hanno fa è successo per OS2 di IBM (chi non ricorda OS2??). Anche questa affermazione è assolutamente discutibile. La sensazione che ho io \u0026ldquo;surfando in rete\u0026rdquo;, è che in realtà il numero di utenti Linux sia in costante aumento\u0026hellip; magari è solo la speranza di uno dei tanti utenti linux, lontana da quella che è invece la realtà.\n","date":"24 juin 2008","externalUrl":null,"permalink":"/it/bill-gates-vs-gpl-license/","section":"Posts","summary":"","title":"Bill Gates vs GPL License","type":"posts"},{"content":"Su Download Blog.it hanno pubblicato oggi il risultato di un sondagio di qualche giorno fa: quante volte al giorno controllate la vostra email?\nA: Tengo la posta elettronica sempre aperta\nB: Qualche volta al giorno\nC: Solo una volta al giorno\nD: Qualche volta alla settimana\nOvviamente nessuno sarà sorpreso dei risultati. Anche solo per il lavoro, la mail è ormai diventata strumento indispensabile al punto che, non poter essere contattati via mail, può significare scarsa considerazione della propria attività lavorativa.\nLa mail però è solo uno dei tanti \u0026ldquo;strumenti\u0026rdquo; che vengono oggi offerti su internet, e quindi la domanda che mi faccio è: potremmo vivere senza internet?\nIo che pongo la domanda rispondo direttamente che per me è una sofferenza non poter avere a disposizione una connessione. Qualsiasi cosa mi serva so che la posso trovare \u0026ldquo;sul Grande Net\u0026rdquo; (la G maiuscola vuole sottolineare il proprietario di internet oggi :P): un numero di telefono, una strada, orari per mezzi di trasporto, informazioni su una città, un convegno, un programma, poter dialogare con altri per risolvere un problema, \u0026hellip; e potremmo andare avanti per mesi ad elencare tutto quello che è possibili fare e trovare sulla rete.\nIn ambito informatico, credo sia impensabile lavorare senza potersi collegare ad internet e cercare soluzioni ai propri problemi. Mi stupisco ancora quando, per impegni di lavoro, mi devo spostare per qualche tempo in grosse società e mi sento dire no, non è possibile accedere ad internet oppure per accedere ad internet devi fare esplicita richiesta al Dott. PincoPallino. Il problema è che poi in queste realtà, quando ci si trova di fronte ad un problema, non si cerca il modo di risolverlo all\u0026rsquo;interno (quindi potremmo usare la parola gratis)\u0026hellip; abbiamo un applicativo sotto mano, lo ha fatto XYZ -\u0026gt; chiamo XYZ e spendo N milioni in consulenza per poter risolvere il mio problema (molto spesso la soluzione e assolutamente banale e richiede 10 minuti di lavoro).\nSe avessi avuto la possibilità di fare una ricerca su Google (è tornata la G maiuscola\u0026hellip; per chi non avesse capito in precedenza) avrei trovato milioni di post su forum, mailing list o anche sul sito del produttore del mio programma la soluzione che mi avrebbe fatto risparmiare.\nComunque, tornando alla domanda del post, quanti di noi oggi sarebbero in grado di vivere (anche se potrei usare un termine più forte come sopravvivere) senza aver accesso ad internet, controllare la mail, scrivere sul blog e tutte le altre attività che ogni giorno ci vedono costretti ad un collegamento?\n","date":"24 juin 2008","externalUrl":null,"permalink":"/it/potremmo-vivere-senza-internet/","section":"Posts","summary":"","title":"Potremmo vivere senza internet?","type":"posts"},{"content":"Incontriamoci 2008. Anche quest\u0026rsquo;anno ByteCode, l\u0026rsquo;azienda per cui lavoro, ha organizzato un evento di incontro/scontro per dipendenti e relative famiglie (ovviamente nessuno si è portato i genitori, sono le famiglie verso il \u0026ldquo;basso\u0026rdquo; quelle considerate ;)) con attività culturalmente interessanti per l\u0026rsquo;ambito lavorativo ma anche di puro divertimento.\nDopo il golf dello scorso anno, questa volta\u0026hellip; GO WET! : interessante 2 giorni di rafting e presentazioni, cibo e passeggiate (sotto l\u0026rsquo;acqua).\nNon sto a descrivere la parte prettamente funny del meetup, della quale però vi lascio qualche immagine (qui a fianco c\u0026rsquo;è una bella slideshow che gira) ed eventualmente leggere sui blog degli altri miei colleghi\u0026hellip; Riassumendo: abbiamo trovato tutti la cosa appassionante e divertente.\nLa parte più interessante, informaticamente parlando, è stata un susseguirsi di interventi sia da parte di esponendi ByteCode che da parte di persone esterne (solo una a dire il vero, ma usare il plurale in questi casi da maggior risalto alla cosa ;)).\nLe presentazioni sono servite per aggiornare tutti sulle innovazioni presenti all\u0026rsquo;interno della società, sia a livello decisionale/strategico, sia a livello di prodotti e tecnologia in fase di studio/sviluppo.\nSegnalo (ovviamente) la mia presentazione di un prodotto OpenSource al quale partecipo attivamente: Symbolic.\nCon relative slide.\nSe intendete visionare, anche solo per curiosità, gli altri interventi sono tutti reperibili online\u0026hellip; qui! ;)\n","date":"23 juin 2008","externalUrl":null,"permalink":"/it/meetup-2008-go-wet/","section":"Posts","summary":"","title":"MeetUp 2008 - Go Wet!","type":"posts"},{"content":"La gara per l\u0026rsquo;appalto delle frequenze del WiMax è ormai stata vinta e tutti avremo consultato almeno una volta l\u0026rsquo;elenco con le relative offerte; da allora, assorbito da mille altre cose in ambito lavorativo, ammetto di essermi impostato in \u0026ldquo;waiting mode\u0026rdquo; aspettando passivamente che qualcun altro si mettesse a documentare dettagliatamente lo stato di avanzamento dei lavori.\nOggi, sfruttando il viaggio in treno (che per li ******* delle ferrovie Nord, è un convoglio dell\u0026rsquo;ante-guerra sprovvisto di aria condizionata\u0026hellip; ma questa è un\u0026rsquo;altra storia\u0026hellip;) e la connessione UMTS \u0026ldquo;offerta\u0026rdquo; per soli 30€ al mese dalla TIM, mi sono messo a fare qualche ricerchina un po\u0026rsquo; dettagliata per cercare di capire meglio come si stavano muovendo le cose in casa WiMax\u0026hellip;\nDi informazioni circa lo stato dei lavori non ne ho trovati, a parte notizie su alcune testate dove ci si lamenta perchè in alcuni paesi mancano le case, distrutte da calamità natuali, e il comune, o chi per esso, ha pubblicizzato l\u0026rsquo;avvento delle nuove connessioni internet disponibili per tutti.\nPerò ho trovato qualcosa di molto più interessante. Dal 2004 la fondazione Ugo Bordoni (http://www.fub.it/) con la collaborazione di alcune grandi società sparse sul territorio italiano, ha condotto alcuni esperimenti con la tecnologia WiMax (http://wimax.fub.it/) e i risultati sono sorprendentemente\u0026hellip; deludenti.\nCito come esempio, giusto perchè ce l\u0026rsquo;ho qui sotto mano, la sperimentazione condotta da Siemes in alcune aree del nord italia (tutti i dettagli li potete comunque trovare sul sito della fondazione http://wimax.fub.it/html_pages/risultati_della_sperimentazione.htm).\nTralasciando la parte prettamente ingegneristica su potenza del segnale, rumore, ecc. che potrebbero risultare ad alcuni non facilmente comprensibili, andiamo a parlare direttamente del dato che sicuramente tutti gli internauti del mondo possono capire al volo: la velocità di trasferimento, o banda passante.\nEbbene dai test effettuati su Milano (per la precisione la sede della Siemens è a Cassina de Pecchi a nord di Milano) nelle viette della città si riceve praticamente 0!!\nVediamo qualche esempio:\nSettore EST\nSS2 - distanza: 1Km - Banda (Mb/s): 5.02\nPosizione 1 - distanza: 0.25Km - Banda (Mb/s): 1.68\nPosizione 5 - distanza: 0.62Km - Banda (Mb/s): 0.84\nPosizione 6 - distanza: 0.71Km - Banda (Mb/s): 0.84\nSettore OVEST\nSS5 - distanza: 2.5Km - Banda (Mb/s): 0.84\nPosizione 1 - distanza: 2.4Km - Banda (Mb/s): KO\nPosizione 2 - distanza: 2.3Km - Banda (Mb/s): KO\nOttimo ad un 1km di distanza dall\u0026rsquo;antenna di trasmissione la velocità è di ben 5Mbit\u0026hellip; beh non male dai, le attuali linee di connessione ADSL dichiarano di andare a 7 ma poi vanno molto meno (dipende dai momenti della giornata). Peccato che il WiMax dovesse garantire velocità stratosferiche coprendo un\u0026rsquo;area di circa 50Km\u0026hellip;\nSecondo i proponenti di WiMAX l\u0026rsquo;ampiezza di banda sarebbe sufficiente a supportare simultaneamente almeno 40 aziende con connettività di tipo T1 e 70 abitazioni con connettività al livello di una DSL da 1 Mbit/s.)\nFonte Wikipedia\nMa procediamo nella lettura: sulla strada a 250 metri dall\u0026rsquo;antenna i Mb diventano 1.68\u0026hellip; sempre meglio; a 2.4Km dall\u0026rsquo;antenna il segnale non lo si riceve nemmeno!!!\nSe continuate a leggere i vari test che sono giunti alla fondazione vi accorgerete che i risultati riprongono più o meno sempre la stessa situazione (ammetto di aver preso quelli forse più disastrosi, ma gli altri, come ho detto, non mostrano una situazione di migliore di questa).\nIl WiMax funziona abbastanza bene finchè si resta in vista dell\u0026rsquo;antenna, quando cominciano ad esserci ostacoli però il segnale deteriora a livelli disastrosi. Quindi per colmare il digital divide che vede ancora molte zone d\u0026rsquo;italia sprovviste di accesso ad internet a banda larga, sarà sufficiente andare a mettere delle antenne WiMax davanti casa degli sfortunati senza ADSL e, naturalmente, collegare gli apparati WiMAX alla rete internet attraverso un cavo in fibra ottiva (da stendere fino praticamente a casa delle persone che ancora non hanno connessione a banda larga)\u0026hellip; allora mi domando: a quel punto non sarebbe meglio fornirgli la connessione via cavo?\nQuindi cambiate gli occhiali perchè per il WiMax dovrete vedere davvero lontano! ;)\nE soprattutto WhyMax???\n","date":"23 juin 2008","externalUrl":null,"permalink":"/it/wimax-ma-funziona-davvero/","section":"Posts","summary":"","title":"WiMax - Ma funziona davvero?","type":"posts"},{"content":"Se, come me del resto, non ricordate l\u0026rsquo;indirizzo del vostro cellulare blutooth e il canale del servizio che dovete configurare (il modem in questo caso), il comando sdptool vi aiuta sicuramente.\nSe non lo avete installato per la fedora si trova nel pacchetto bluez-util (FC9: bluez-utils-3.30-2.fc9.x86_64), quindi un bel yum install e via.\nsdptool browse\nIn questo modo avrete avviato la scansione di tutti i dispositivi bluetooth raggiungibili e relativi servizi esposti.\nL\u0026rsquo;output che otterrete sarà qualcosa di simile a questo:\n[mmornati@bcmmornati ~]$ sdptool browse\nInquiring \u0026hellip;\nBrowsing 00:17:B0:FB:6E:7C \u0026hellip;\nService Name: OBEX File Transfer\nService RecHandle: 0x10002\nService Class ID List:\n\u0026ldquo;OBEX File Transfer\u0026rdquo; (0x1106)\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 10\n\u0026ldquo;OBEX\u0026rdquo; (0x0008)\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026ldquo;OBEX File Transfer\u0026rdquo; (0x1106)\nVersion: 0x0100\nService Name: OBEX Object Push\nService RecHandle: 0x10003\nService Class ID List:\n\u0026ldquo;OBEX Object Push\u0026rdquo; (0x1105)\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 9\n\u0026ldquo;OBEX\u0026rdquo; (0x0008)\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026ldquo;OBEX Object Push\u0026rdquo; (0x1105)\nVersion: 0x0100\nService Name: Imaging\nService RecHandle: 0x10004\nService Class ID List:\n\u0026ldquo;Imaging Responder\u0026rdquo; (0x111b)\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 15\n\u0026ldquo;OBEX\u0026rdquo; (0x0008)\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026ldquo;Imaging\u0026rdquo; (0x111a)\nVersion: 0x0100\nService Name: SyncMLClient\nService RecHandle: 0x10005\nService Class ID List:\nUUID 128: 00000002-0000-1000-8000-0002ee000002\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 11\n\u0026ldquo;OBEX\u0026rdquo; (0x0008)\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026quot;\u0026quot; (0x00000002-0000-1000-8000-0002ee000002)\nVersion: 0x0100\nService Name: Nokia OBEX PC Suite Services\nService RecHandle: 0x10006\nService Class ID List:\nUUID 128: 00005005-0000-1000-8000-0002ee000001\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 12\n\u0026ldquo;OBEX\u0026rdquo; (0x0008)\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026quot;\u0026quot; (0x00005005-0000-1000-8000-0002ee000001)\nVersion: 0x0100\nService Name: Dial-Up Networking\nService RecHandle: 0x10007\nService Class ID List:\n\u0026ldquo;Dialup Networking\u0026rdquo; (0x1103)\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 3\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026ldquo;Dialup Networking\u0026rdquo; (0x1103)\nVersion: 0x0100\nService Name: Hands-Free Audio Gateway\nService RecHandle: 0x1000a\nService Class ID List:\n\u0026ldquo;Handfree Audio Gateway\u0026rdquo; (0x111f)\n\u0026ldquo;Generic Audio\u0026rdquo; (0x1203)\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 1\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026ldquo;Handfree Audio Gateway\u0026rdquo; (0x111f)\nVersion: 0x0101\nService Name: Headset Audio Gateway\nService RecHandle: 0x1000b\nService Class ID List:\n\u0026ldquo;Headset Audio Gateway\u0026rdquo; (0x1112)\n\u0026ldquo;Generic Audio\u0026rdquo; (0x1203)\nProtocol Descriptor List:\n\u0026ldquo;L2CAP\u0026rdquo; (0x0100)\n\u0026ldquo;RFCOMM\u0026rdquo; (0x0003)\nChannel: 2\nLanguage Base Attr List:\ncode_ISO639: 0x454e\nencoding: 0x6a\nbase_offset: 0x100\nProfile Descriptor List:\n\u0026ldquo;Headset\u0026rdquo; (0x1108)\nVersion: 0x0100\nAmmetto di non avere idea di che telefono siano le informazioni qui sopra riportate (ho lanciato il comando in treno e il mio cellulare è invisibile alle ricerche; fortunatamente c\u0026rsquo;è sempre qualcuno che non tiene troppo alla sicurezza ;)\nDa qui però, come detto in precedenza, avrete tutto quanto è necessario per poter effettuare la vostra configurazione.\nNel caso dell\u0026rsquo;esempio, l\u0026rsquo;indirizzo (paragonabile al mac address delle schede di rete) è:\n00:17:B0:FB:6E:7C\nmentre il canale del servizio che ci interessa è quello identificato (nel caso dei Nokia) dal nome \u0026ldquo;Dial-Up Networking\u0026rdquo;, sul cellulare della sfortunata cavia è il 3.\nNotate bene che varia molto da modello a modello anche il canale, quindi è assolutamente necessario che recuperiate i vostri dati per poter effettuare la configurazione.\nA questo punto, con le informazioni appena prese andiamo a configurarci un bel linux device.\nProcediamo innanzitutto verificando che il nostro telefono funzioni davvero:\nrfcomm connect /dev/rfcomm0 00:17:B0:FB:6E:7C 3\ndove /dev/rfcomm0 è il nome che abbiamo scelto per il nostro dispositivo; i numeri che seguono dovresti riconoscerli da soli.\nSe tutto va come dovrebbe andare dovreste, come prima cosa, ritrovarvi la richiesta di paring (inserimento di un pin su entrambi i dispositivi) che permetterà di aprire il canale e successivamente un bel messaggino vi dirà che è connesso e dovrete premere CTRL + C per chiudere la comunicazione con il vostro cellulare.\nVisto che dover fare questa cosa ogni volta che dovete collegarvi via modem non per niente comoda, andiamo a generarci una hard-configuration che resti indelebilmente scritta sul vostro disco (almeno fino a che non cambierete distribuzione ;)).\nModifichiamo il file /etc/bluetooth/rfcomm.conf con i parametri che già dovreste conoscere a memoria (io ribadisco che non li ho mai imparati, quindi direi che non è preoccupante se non li si ricorda):\n#\n# RFCOMM configuration file.\n#\nrfcomm0 {\n# Automatically bind the device at startup\nbind yes;\n# Bluetooth address of the device\ndevice 00:17:B0:FB:6E:7C;\n# RFCOMM channel for the connection\nchannel 3;\n# Description of the connection\ncomment \u0026ldquo;My Nokia\u0026rdquo;;\n}\nDopo averlo aperto avrete notato che è praticamente già tutto scritto dentro il file, dovrete solo decommentare e personalizzare ;)\nOra avrete il device rfcomm0 configurato e pronto all\u0026rsquo;occorrenza.\nPer poter invece fisicamente collegarsi ad internet serve dell\u0026rsquo;altro. Io prediligo Gnome PPP perchè mi mostra anche il tempo di connessione (pagando ad ore non fa male vederlo), però se non volete assolutamente usare un applicativo grafico basta e avanza wvdial (che è comunque ciò che usa anche Gnome PPP).\nNon staremo a dilungarci molto sulla configurazione di Gnome PPP perchè è abbastanza banale e perchè i parametri dipendono dall\u0026rsquo;operatore telefonico con cui vi dovete collegare.\nLa cosa importante (sia in GnomePPP che in wvdial) è che mettiamo come Modem /dev/rfcommo (o quello che vi siete scelti in fase di configurazione).\nThat\u0026rsquo;s all!\n","date":"22 mai 2008","externalUrl":null,"permalink":"/it/configuration-cellmodem-bluetooth/","section":"Posts","summary":"","title":"Configuration Cell/Modem Bluetooth","type":"posts"},{"content":"","date":"22 mai 2008","externalUrl":null,"permalink":"/it/tags/fedora/","section":"Tags","summary":"","title":"Fedora","type":"tags"},{"content":"Avevo deciso di aspettare di più per migrare alla nuova release di Fedora, la 9 per l\u0026rsquo;appunto, ma alla fine non ho resistito. Anzi, secondo i miei standard ho anche aspettato troppo! Con la 8 avevo formattato il portatile ed aggiornato il giorno stesso dell\u0026rsquo;uscita\u0026hellip; stavolta ho atteso quasi 2 settimane! :)\nLa cosa che mi ha colpito di più dopo i primi due giorni di utilizzo è stata la velocità di accensione e spegnimento. Impiega davvero pochissimo (dopo aver disabilitato il servizio sendmail che viene sempre installato ed attivato ma credo che nessuno lo usi davvero su un portatile).\nUn altro vantaggio, per il quale però al momento non sa dare una vera spiegazione, è che la durata della batteria è migliorata. Non ho guadagnato moltissimo eh.. .solo una ventina di minuti rispetto alla release 8, però è già qualcosa. Il fatto che non sappia dare una spiegazione, è semplicemente perchè posso immaginare le migliorie introdotte a livello di risparmio energetico nel nuovo kernel, però non avendo per il momento letto alcuna documentazione al riguardo, posso solo riportare una nota circa il reale incremento di durata e niente più.\nI driver NVidia sfortunatamente non sono ancora stati rilasciati. Il portatile è ovviamente utilizzabile lo stesso e senza nemmeno troppi rallentamenti grafici, però non ho ancora potuto gustarmi compiz su questa nuova release! ;) Aspettiamo e speriamo che la cara vecchia \u0026ldquo;invidia\u0026rdquo; faccia il suo corso e si decida a lavorare per il mondo!\nLa cosa che ancora una volta mi lascia abbastanza perplesso è il java. Sfortunatamente dovendo lavoraci mi serve installarlo ogni volta :)\nDi default vengono installate le JRE di OpenJDK, nel mio caso a 64 bit, che ovviamente sono insufficiente per qualsiasi programmatore Java.\nCome primo tentativo ho provato ad installare le ultime JDK di Sun (1.6.0.6) nella versione 32 bit, ma sfortunatamente andando ad avviare programmi (tipo un IDE per programmare) che richiedono la visualizzazione di finestrelle dentro X, viene fatto riferimento a librerie del SO a 32bit che in questa nuova versione non vengono installate.\nQuindi, dovendo andare a scegliere se installare le JDK a 64bit o una libreria a 32bit su un sistema a 64bit, ho preferito mantenere tutto con 32bit in più.\nAnche per questa prova il primo tentativo l\u0026rsquo;ho fatto dando fiducia a Sun che non ha tradito: installato dall\u0026rsquo;rpm settata la JAVA_HOME e tutto funzionava benone (se occupare il doppio della ram rispetto alle JDK 32bit significa andare benone, ovviamente).\nColto da curiosità ho però voluto provare le OpenJDK: malgrado il nome faccia pensare che le OpenJDK siano le JDK non è così, per poter avere a disposizione anche le JDK vere e proprie è necessario installare il pacchetto devel.\nIl mio test mirava soprattutto a verificare se lo spreco di ram era esattamente lo stesso (confermo che è esattamente lo stesso)\u0026hellip; peccato però che, soprattutto i caratteri mostrati a video dentro le applicazioni java, non siano molto leggibili. Quindi si possono tranquillamente usare anche le OpenJDK, se però, come me amate leggere bene (e soprattutto non vedete molto bene) consiglio di restare fedeli ancora a mamma Sole! ;)\n","date":"22 mai 2008","externalUrl":null,"permalink":"/it/fedora-9/","section":"Posts","summary":"","title":"Fedora 9","type":"posts"},{"content":"","date":"22 mai 2008","externalUrl":null,"permalink":"/it/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"Bonjour ! Je suis Marco Mornati, ingénieur logiciel basé à Lille, en France, où je travaille chez Decathlon. Je construis des logiciels, j\u0026rsquo;administre ma propre infrastructure et j\u0026rsquo;écris sur ce que j\u0026rsquo;apprends en chemin.\nCe que je fais # Je suis curieux de toute la stack — d\u0026rsquo;un single board computer branché sur mon réseau local aux services tolérants aux pannes en production. Concrètement, je passe l\u0026rsquo;essentiel de mon temps sur :\nL\u0026rsquo;IA et les agents de code — serveurs MCP, firewall de secrets pour les charges LLM, mémoire persistante pour les coding agents, et comment construire des pipelines agentiques fiables. Le self-hosting — mon VPS, des workflows de déploiement avec Coolify, et tirer le meilleur de l\u0026rsquo;hardware abordable. L\u0026rsquo;open hardware et la domotique — Home Assistant, Zigbee, des expériences ESP32 et quelques projets d\u0026rsquo;analyse énergétique pilotés (aussi) par les panneaux solaires de ma toiture. L\u0026rsquo;expérience développeur — instructions globales pour Copilot/les agents, CI/CD et outils qui accélèrent le quotidien. Open source # Une grande partie de ce que je construis finit en open source sur GitHub. Quelques projets auxquels je tiens particulièrement :\ndocker-mock-rpmbuilder — construire des RPM sur n\u0026rsquo;importe quelle plateforme avec le projet Mock. springboot-osgi-sample — OSGi dans une application Spring Boot. ha-energy-analysis — analyse énergétique Home Assistant pour vérifier si panneaux et batterie rentabilisent vraiment… que les chiffres me donnent raison ou non. home-assistant-csnet-home — piloter les pompes à chaleur Hitachi (CSNet / atw-iot-01) depuis Home Assistant. leanproxy-mcp — un \u0026ldquo;pare-feu de tokens\u0026rdquo; pour MCP qui réduit les coûts de tokens LLM tout en gardant les secrets hors des prompts. home-assistant-mel-garbage-collection — intégration Home Assistant pour la collecte des déchets MEL (Lille). gphoto2proton — migrer un export Google Photos vers Proton Drive, avec gestion des albums, correction des EXIF et streaming. ruby-noise-detection — un vieux script Ruby de détection de bruit. Gardé pour la nostalgie — il fonctionne toujours. Ce blog # Il vivait avant sur Hashnode. Il tourne désormais ici — en self-hosting sur mon VPS, construit avec Hugo et le thème Blowfish, publié via git + CI/CD car la meilleure façon de posséder son contenu est de le garder dans un dépôt.\nLa façon la plus rapide de me joindre est GitHub (ou @marcomornati sur X).\n","externalUrl":null,"permalink":"/fr/page/about/","section":"Pages","summary":"","title":"À propos","type":"page"},{"content":"All the posts I\u0026rsquo;ve published, most recent first.\n","externalUrl":null,"permalink":"/archive/","section":"Mornati Blog","summary":"","title":"Archive","type":"page"},{"content":"","externalUrl":null,"permalink":"/fr/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/fr/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/fr/page/","section":"Pages","summary":"","title":"Pages","type":"page"},{"content":"Des choses que j\u0026rsquo;utilise vraiment et que je recommanderais. Cette liste évolue — le contenu est en Markdown simple, vous pouvez donc ouvrir la source et voir ce qui a changé.\nSelf-hosting # Coolify — ma façon préférée de faire tourner des apps sur un VPS. Blowfish — le thème Hugo de ce blog. Services # Cloudinary — hébergement, stockage et CDN d\u0026rsquo;images. Umami — analytics respectueuses de la vie privée. Autre # D\u0026rsquo;autres recommandations à venir. (Modifiez cette page pour ajouter les vôtres.)\n","externalUrl":null,"permalink":"/fr/page/recommandations/","section":"Pages","summary":"","title":"Recommandations","type":"page"},{"content":"","externalUrl":null,"permalink":"/fr/series/","section":"Series","summary":"","title":"Series","type":"series"}]