<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Raieta</title>
    <description>The latest articles on DEV Community by Andrew Raieta (@miwaty).</description>
    <link>https://dev.to/miwaty</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F875668%2F8999f7cb-c625-4b78-a7d9-8aa0204f7635.jpg</url>
      <title>DEV Community: Andrew Raieta</title>
      <link>https://dev.to/miwaty</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miwaty"/>
    <language>en</language>
    <item>
      <title>PawGuard — AI-Powered Breed Health Risk Reports for Dogs</title>
      <dc:creator>Andrew Raieta</dc:creator>
      <pubDate>Sat, 15 Aug 2026 18:06:40 +0000</pubDate>
      <link>https://dev.to/miwaty/pawguard-ai-powered-breed-health-risk-reports-for-dogs-1kj2</link>
      <guid>https://dev.to/miwaty/pawguard-ai-powered-breed-health-risk-reports-for-dogs-1kj2</guid>
      <description>&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PawGuard&lt;/strong&gt; — you punch in your dog's breed, age, weight, sex, and it tells you which hereditary conditions that breed actually gets, with real screening recommendations and a timeline from puppy to senior. Built entirely around Google AI/Gemini.&lt;/p&gt;

&lt;p&gt;You can also just upload a photo instead of typing anything: Gemini Vision looks at it and guesses the breed, body condition, and rough age, then pre-fills the form for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8i5702orb2lvbgnw66ny.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8i5702orb2lvbgnw66ny.jpeg" alt="PawGuard dog profile form after uploading a photo, with breed, name, and body condition auto-filled by Gemini Vision" width="800" height="1541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://pawguard-v45n.onrender.com" rel="noopener noreferrer"&gt;https://pawguard-v45n.onrender.com&lt;/a&gt;&lt;br&gt;&lt;br&gt;
💻 &lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/araieta/PawGuard" rel="noopener noreferrer"&gt;https://github.com/araieta/PawGuard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(The app supports both Italian and English via a toggle in the header — screenshots below are in English.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;By day I'm a software engineer doing cybersecurity/SOC work with Splunk, and lately I've been sliding over into AI/LLM security. When the weekend theme turned out to be International Dog Day, I didn't want to just ship another "here's a cute breed identifier" demo — I wanted something an actual dog owner would open more than once.&lt;/p&gt;

&lt;p&gt;Also, honest disclosure: I don't have a dog. Don't have the space, don't have the budget right now. But a good chunk of my friends do, and this is the kind of thing I'd genuinely send them, not just something I built to check a box for the challenge.&lt;/p&gt;

&lt;p&gt;Here's the actual problem I was trying to solve: most first-time owners have zero idea their breed comes with specific, well-documented risks until something already went wrong. Nobody tells a new Basset Hound owner that the breed's whole body shape makes disc disease a real thing to watch for. Nobody tells a Boxer owner they should be thinking about cardiac screening (ARVC) starting pretty young. PawGuard is my attempt at surfacing that stuff &lt;em&gt;before&lt;/em&gt; it turns into an emergency vet bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Profile step&lt;/strong&gt; — name, breed, age, weight, sex (or just drop in a photo and let Gemini fill most of it in)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk engine&lt;/strong&gt; — the backend checks the breed against a knowledge base I curated by hand (prevalence, severity, typical age of onset for each condition), then bumps the risk score based on stuff specific to &lt;em&gt;this&lt;/em&gt; dog — like weight sitting outside the ideal range for that breed/age&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report generation&lt;/strong&gt; — Gemini takes the structured risk output and turns it into an actual explanation per condition, plus a screening timeline (puppy → adult → senior)
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F209meb1lmtiqkvuc4zrp.jpeg" alt="PawGuard condition card showing a specific hereditary risk with a color-coded risk bar and breed-specific explanation" width="800" height="1655"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fby7aqonuwgjw0ntsb8e8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fby7aqonuwgjw0ntsb8e8.jpeg" alt="PawGuard condition card continued, showing the personalized screening recommendation and priority level" width="800" height="1662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PDF export&lt;/strong&gt; — a proper document, formatted like something a vet clinic would actually print, not a screenshot of the webpage stuffed into a PDF&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5sg3zqyt1h8ge1f58fwf.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5sg3zqyt1h8ge1f58fwf.jpeg" alt="PawGuard preventive care timeline broken down into puppy and adult life stages" width="800" height="1663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fed4fwpfxaochefgefs53.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fed4fwpfxaochefgefs53.jpeg" alt="PawGuard preventive care timeline continued, showing senior stage recommendations" width="800" height="1662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foslhjpejnxluzyn6xmww.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foslhjpejnxluzyn6xmww.jpeg" alt="PawGuard report page with a Download PDF button below the preventive timeline" width="800" height="1660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg24pb5v2c93u6a69uw6k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg24pb5v2c93u6a69uw6k.jpeg" alt="PawGuard completed report showing patient data and executive summary, with all three flow steps marked done" width="800" height="1541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Google AI Usage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Google AI (Gemini)&lt;/strong&gt; does the heavy lifting here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vision&lt;/strong&gt;: breed guess, body condition, apparent age, straight from a photo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured generation&lt;/strong&gt;: every report comes back as validated JSON, not free text — the risk engine's output gets turned into breed-specific explanations, not generic filler&lt;/li&gt;
&lt;li&gt;Ollama Cloud is in there too, as a vision fallback for when I blew through Gemini's free tier mid-testing (which happened more than once, ask me how I know)
&lt;strong&gt;Backend&lt;/strong&gt;: Python (FastAPI), a breed condition knowledge base I built from actual public veterinary references — not something the model hallucinated — deployed on Render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;: Vue 3 straight from a CDN, no build step, because it's a weekend project and I didn't need the overhead. Custom visual identity, and the PDF export is styled completely separately from the web UI — it reads like an actual document, not a printout of a webpage.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bug I'm Actually Kind of Proud Of
&lt;/h2&gt;

&lt;p&gt;While stress-testing with weird inputs, I entered a Dogue de Bordeaux puppy at 7.8kg / 6 months old — real breed average at that age is closer to 35kg. The report just said the weight "deviated from the ideal range," no direction given, which is basically useless and could read as a false alarm to a real user. Fixed it by adding a plausibility check in the risk engine and forcing the generated text to say &lt;em&gt;which&lt;/em&gt; direction (underweight vs. overweight), because clinically those two things mean completely different stuff for the same condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;If I keep poking at this after the weekend: more breeds in the knowledge base (right now it covers the ~30-40 most common ones), a side-by-side breed comparison for people deciding between two, maybe a lightweight way to log actual vet visits against the recommended timeline.&lt;/p&gt;




&lt;p&gt;Built solo this weekend for the DEV Weekend Challenge: Dog Days Edition. Ask me anything about the risk engine or the Gemini structured-output setup in the comments.&lt;/p&gt;

</description>
      <category>weekendchallenge</category>
      <category>python</category>
      <category>vue</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>I Built a Fully Local OSINT Agent with Ollama, LangChain, Telegram and Qwen3.5 14B — Running 24/7 on My Homelab, Zero Cloud, Zero Compromises</title>
      <dc:creator>Andrew Raieta</dc:creator>
      <pubDate>Sun, 12 Apr 2026 21:45:28 +0000</pubDate>
      <link>https://dev.to/miwaty/i-built-a-fully-local-osint-agent-with-ollama-langchain-telegram-and-qwen35-14b-running-247-1pfk</link>
      <guid>https://dev.to/miwaty/i-built-a-fully-local-osint-agent-with-ollama-langchain-telegram-and-qwen35-14b-running-247-1pfk</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Fully Local OSINT Agent with Ollama, LangChain, Telegram and Qwen3.5 14B — Running 24/7 on My Homelab, Zero Cloud, Zero Compromises
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt; — This project was built for &lt;strong&gt;educational purposes only&lt;/strong&gt;, as a hands-on way to learn LangChain, local LLMs and OSINT tooling. Every technique described here should only be used on targets you have explicit written authorisation to analyse. Unauthorised use of OSINT tools may be illegal in your jurisdiction. Always act ethically and responsibly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been wanting to seriously learn LangChain for a while. Not just tutorials — actually build something real that I'd use.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;OSINT Marin AI&lt;/strong&gt;: a fully local OSINT agent running on my homelab, accessible from anywhere via Telegram, powered by &lt;strong&gt;Qwen3.5 14B&lt;/strong&gt; running locally via Ollama, with Tor for IP rotation, SQLite for caching, and Matplotlib for statistical charts sent directly to my phone.&lt;/p&gt;

&lt;p&gt;No cloud. No API keys. No data leaving my network. Just Python, open-source tools, and a lot of trial and error.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why local?
&lt;/h2&gt;

&lt;p&gt;If you work anywhere near cybersecurity, you already know the answer. Sending queries about IP addresses, domains, Instagram profiles or email addresses to a cloud API means that data is leaving your network and hitting someone else's servers. In a professional context that's often a hard no.&lt;/p&gt;

&lt;p&gt;Local models solve this completely. Everything stays on your hardware, under your control, with no third-party visibility into what you're analysing.&lt;/p&gt;

&lt;p&gt;For this project I used &lt;strong&gt;Qwen3.5 14B&lt;/strong&gt; in Q6_K quantization, served by Ollama. Thinking mode disabled — it adds latency with no real benefit for this use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the agent can actually do
&lt;/h2&gt;

&lt;p&gt;I'll be concrete. These are the Telegram commands that work right now:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instagram:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/analizza_profilo @username&lt;/code&gt; — full profile metadata + AI summary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/scarica_foto @username&lt;/code&gt; — downloads photos, shows a paginated interactive list with buttons to view each photo or its metadata (likes, comments, date, hashtags, caption) directly in Telegram&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/scarica_reel @username&lt;/code&gt; — downloads reels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/stories @username&lt;/code&gt; — downloads stories (requires auth)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/foto_profilo @username&lt;/code&gt; — downloads and sends the profile picture&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/statistiche @username&lt;/code&gt; — generates and sends 7 statistical charts as PNG images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Web &amp;amp; OSINT:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/analizza_sito https://...&lt;/code&gt; — full site analysis with AI summary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/whois dominio.com&lt;/code&gt; — WHOIS + DNS records + AI interpretation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/sherlock username&lt;/code&gt; — username search across platforms + AI digital profile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/ip 1.2.3.4&lt;/code&gt; — geolocation, ISP, ASN + AI comment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/email info@x.com&lt;/code&gt; — MX records, provider, domain status + AI analysis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/telefono +39...&lt;/code&gt; — carrier, country, number type + AI comment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/chiedi &amp;lt;domanda&amp;gt;&lt;/code&gt; — free natural language question to the LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every command that hits an external service first checks the local SQLite database. If the data is already there, it returns it instantly without making a new request.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python-telegram-bot 21.6     ← user interface
LangChain 0.3.7              ← agent framework
LangChain-Ollama 0.2.1       ← LLM connector
Ollama                       ← local model server
Qwen3.5 14B (Q6_K)          ← the model (no thinking mode)
Instaloader 4.13.1           ← Instagram scraping
SQLAlchemy 2.0.35 + SQLite   ← local database
Matplotlib 3.9.2             ← statistical charts
Sherlock                     ← username OSINT
python-whois + dnspython     ← WHOIS and DNS
phonenumbers                 ← phone number analysis
stem 1.8.2                   ← Tor integration
Loguru 0.7.2                 ← structured logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You (Telegram)
      ↓
  Telegram Bot (python-telegram-bot)
      ↓
  handlers.py — command routing + auth check
      ↓
  tools/ — instagram, web, osint, stats
      ↓
  SQLite DB — always checked before external requests
      ↓
  External sources (Instagram, ip-api, Sherlock...)
      ↓ (optional)
  Tor proxy — IP rotation for Instagram requests
      ↓
  agent/llm.py — ChatOllama, generates AI summary
      ↓
  Back to Telegram — structured data + AI block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM is the last step, not the first. The tools collect the data, the model synthesises it. If Ollama isn't running, the bot works anyway — the AI block is just skipped silently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The caching layer — the decision I'm most proud of
&lt;/h2&gt;

&lt;p&gt;Every external request is expensive: rate limits, latency, risk of getting blocked. So before every call to Instagram, ip-api, Sherlock or WHOIS, the agent checks SQLite first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analizza_profilo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Check local DB first — never hit Instagram twice for the same target
&lt;/span&gt;    &lt;span class="n"&gt;profilo_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;profilo_db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Profile &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; found in local database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{...}&lt;/span&gt;  &lt;span class="c1"&gt;# instant response
&lt;/span&gt;
    &lt;span class="c1"&gt;# Only here if not cached
&lt;/span&gt;    &lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_get_loader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autenticato&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instaloader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_username&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dati&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;save_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dati&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dati&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five tables: &lt;code&gt;profiles&lt;/code&gt;, &lt;code&gt;posts&lt;/code&gt;, &lt;code&gt;websites&lt;/code&gt;, &lt;code&gt;osint_results&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;. Every result stored with full metadata. Over time the local database becomes genuinely useful — a growing knowledge base of everything you've ever analysed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tor integration for IP rotation
&lt;/h2&gt;

&lt;p&gt;Instagram rate-limits aggressively. After a few requests, you start getting 401/403 from their GraphQL API. My solution: route Instagram traffic through Tor and rotate the circuit when blocked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;tor
&lt;span class="c"&gt;# Add to torrc:&lt;/span&gt;
&lt;span class="c"&gt;# ControlPort 9051&lt;/span&gt;
brew services start tor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Python, the &lt;code&gt;utils/tor.py&lt;/code&gt; module handles circuit rotation via &lt;code&gt;stem&lt;/code&gt;. When Instaloader hits a block, the agent automatically requests a new Tor circuit and retries. Not perfect, but it works well enough for homelab use.&lt;/p&gt;




&lt;h2&gt;
  
  
  The LLM integration — keeping it simple
&lt;/h2&gt;

&lt;p&gt;I didn't build a complex ReAct agent for this. The pattern is simpler and more reliable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool runs, collects structured data&lt;/li&gt;
&lt;li&gt;Data is sent to Telegram as formatted text&lt;/li&gt;
&lt;li&gt;Same data is passed to &lt;code&gt;_llm_analizza()&lt;/code&gt; which selects the right prompt for that data type&lt;/li&gt;
&lt;li&gt;Model generates a natural language summary&lt;/li&gt;
&lt;li&gt;Summary sent to Telegram as a separate &lt;strong&gt;Analisi AI&lt;/strong&gt; block
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOllama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen3.5:14b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OLLAMA_BASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# deterministic — no hallucination games
&lt;/span&gt;    &lt;span class="n"&gt;num_predict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temperature 0.1 because I want the model to interpret data, not invent it. The system prompt instructs it to never speculate beyond what the tools returned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 7 statistical charts
&lt;/h2&gt;

&lt;p&gt;For Instagram profiles, &lt;code&gt;/statistiche @username&lt;/code&gt; generates and sends 7 PNG charts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monthly posting frequency&lt;/strong&gt; — bar chart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Most active hours&lt;/strong&gt; — heatmap (day of week × hour)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engagement rate over time&lt;/strong&gt; — line chart (likes + comments / followers × 100)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Like evolution&lt;/strong&gt; — line chart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top hashtags&lt;/strong&gt; — horizontal bar chart (top 20)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content type breakdown&lt;/strong&gt; — pie chart (photo / video / reel)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average caption length by month&lt;/strong&gt; — bar chart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All generated with Matplotlib in &lt;code&gt;Agg&lt;/code&gt; mode (no display needed), saved to &lt;code&gt;/tmp/&lt;/code&gt;, sent to Telegram, then deleted. The underlying data comes from SQLite — no additional Instagram requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project structure — designed to be extended
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;osint-marin-ai/
├── main.py               ← entrypoint
├── agent/
│   └── llm.py            ← ChatOllama + verifica_ollama + analizza_con_llm
├── bot/
│   ├── handlers.py       ← all Telegram command handlers
│   └── keyboards.py      ← inline keyboards
├── tools/
│   ├── instagram.py      ← Instaloader
│   ├── web.py            ← site analysis
│   ├── osint.py          ← Sherlock, IP, email, phone, WHOIS
│   └── stats.py          ← Matplotlib charts
├── db/
│   ├── models.py         ← SQLAlchemy models
│   └── database.py       ← CRUD + always-check-db-first pattern
└── utils/
    ├── logger.py         ← Loguru
    └── tor.py            ← Tor proxy + circuit rotation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a new tool takes four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a file in &lt;code&gt;/tools/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Save results via &lt;code&gt;db/database.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Register the command in &lt;code&gt;bot/handlers.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add a prompt entry in &lt;code&gt;_llm_analizza()&lt;/code&gt; if you want AI summaries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the entire extension contract. No hidden coupling, everything is documented in Italian with docstrings.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually learned building this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LangChain is powerful but you don't always need the full agent loop.&lt;/strong&gt; For most of my commands, a simple tool → LLM → response pattern is faster, more reliable and easier to debug than a full ReAct agent. I'll use LangGraph for the more complex multi-step workflows I'm planning next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local LLMs are genuinely usable now.&lt;/strong&gt; A year ago this would have been frustratingly slow. Today, with Q6_K quantization and thinking mode disabled, the model responds fast enough for interactive Telegram use. The ecosystem has caught up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching is not optional.&lt;/strong&gt; Instagram will block you. Sherlock takes 60-120 seconds. Without the SQLite caching layer this would be unusable. Cache everything, always check the database first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tor helps, but isn't a silver bullet.&lt;/strong&gt; Instagram's bot detection is sophisticated. Authenticated requests with a dedicated account are still more reliable than Tor rotation for heavy scraping.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LangGraph&lt;/strong&gt; for multi-step OSINT workflows (gather → correlate → report)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SearXNG&lt;/strong&gt; for fully local web search integrated into the agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF report generation&lt;/strong&gt; — export a complete OSINT report from Telegram&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled monitoring&lt;/strong&gt; — alert when a tracked profile changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Face recognition&lt;/strong&gt; on downloaded photos&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I started this to learn LangChain. I ended up with a tool I actually use. That's the best possible outcome from a homelab project built for educational purposes.&lt;/p&gt;

&lt;p&gt;If you're in security and haven't explored local LLMs yet — the barrier is lower than you think. Ollama makes model management trivial, LangChain handles the agent plumbing, and python-telegram-bot gives you a mobile interface in 50 lines of code.&lt;/p&gt;

&lt;p&gt;The stack is mature. The models are capable. The only thing missing is someone building with them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by miwaty — cybersecurity enthusiast, homelab builder, eternal work in progress.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>cybersecurity</category>
      <category>llm</category>
      <category>python</category>
    </item>
    <item>
      <title>How I Used the Model Context Protocol (MCP) to Coordinate My DIY Smart Home</title>
      <dc:creator>Andrew Raieta</dc:creator>
      <pubDate>Sun, 06 Jul 2025 20:50:23 +0000</pubDate>
      <link>https://dev.to/miwaty/how-i-used-the-model-context-protocol-mcp-to-coordinate-my-diy-smart-home-109m</link>
      <guid>https://dev.to/miwaty/how-i-used-the-model-context-protocol-mcp-to-coordinate-my-diy-smart-home-109m</guid>
      <description>&lt;h1&gt;
  
  
  How I Used the Model Context Protocol (MCP) to Coordinate My DIY Smart Home
&lt;/h1&gt;

&lt;p&gt;In recent months, I embarked on a personal project to upgrade my home with a smart automation system. The challenge was to integrate multiple components—motion sensors, temperature sensors, lights, and an IP camera—into a cohesive and context-aware environment.&lt;/p&gt;

&lt;p&gt;Initially, each component was working in isolation. A motion sensor could turn on a light, a camera could start recording when triggered, and the temperature sensor could send its data periodically. But none of these devices had any understanding of the overall context. This limitation became apparent as the system grew more complex.&lt;/p&gt;

&lt;p&gt;This article explores how I introduced a custom implementation of a &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; to unify the behavior of all these components and create a more intelligent and coordinated smart home system.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Overview
&lt;/h2&gt;

&lt;p&gt;The hardware and software stack included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mini PC running Ubuntu acting as the central server&lt;/li&gt;
&lt;li&gt;ESP32 boards equipped with motion and temperature sensors, communicating via MQTT&lt;/li&gt;
&lt;li&gt;A Raspberry Pi connected to an IP camera&lt;/li&gt;
&lt;li&gt;Smart lights controlled via RESTful API&lt;/li&gt;
&lt;li&gt;A Python-based automation controller&lt;/li&gt;
&lt;li&gt;A WebSocket server for context synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially, each component operated using simple event-action scripts. For example, when the motion sensor detected movement, a script would trigger the lights to turn on. However, the logic quickly became fragile and difficult to manage as additional devices were added.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Issue: Lack of Shared Context
&lt;/h2&gt;

&lt;p&gt;The primary issue was the absence of a shared understanding of the environment. Consider the following scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The lights turned on even when I was not home.&lt;/li&gt;
&lt;li&gt;The IP camera kept recording even while I was in the living room.&lt;/li&gt;
&lt;li&gt;Motion detection events were triggering the same reactions repeatedly, regardless of other conditions.&lt;/li&gt;
&lt;li&gt;Environmental conditions such as brightness or time of day were not considered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these problems stemmed from the lack of a &lt;strong&gt;global state&lt;/strong&gt; that all components could refer to. Each device operated based on its own inputs, without knowledge of what others were sensing or deciding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Model Context Protocol (MCP)
&lt;/h2&gt;

&lt;p&gt;To solve this, I implemented a lightweight version of the Model Context Protocol. The goal was to establish a &lt;strong&gt;centralized context server&lt;/strong&gt; that all components could publish to and subscribe from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Principles
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Each agent (sensor, light, camera, etc.) publishes its &lt;strong&gt;local state&lt;/strong&gt; to a central context server.&lt;/li&gt;
&lt;li&gt;The server maintains a &lt;strong&gt;global context model&lt;/strong&gt; in memory, updated incrementally as new events arrive.&lt;/li&gt;
&lt;li&gt;When significant changes occur in the context, &lt;strong&gt;interested agents are notified&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Agents react &lt;strong&gt;locally&lt;/strong&gt;, using the latest context to decide whether or not to act.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach decouples the behavior logic from hardcoded trigger rules and instead allows decisions to be made based on consistent, shared context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;p&gt;The Context Server was implemented in Python using the &lt;code&gt;asyncio&lt;/code&gt; and &lt;code&gt;websockets&lt;/code&gt; libraries. MQTT was used for communication with ESP32 devices, and TinyDB was used to persist the context state for inspection and recovery.&lt;/p&gt;

&lt;p&gt;Each component sends updates in JSON format. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"motion_sensor_living_room"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"motion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-07-06T14:21:45Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The context server processes this update and modifies the global context accordingly. A simplified context might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"presence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"motion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_dark"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;27.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_motion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-07-06T14:21:45Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The context server then notifies all subscribers (e.g., the light controller, the camera module) about the updated context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Case: Automated Presence Management
&lt;/h2&gt;

&lt;p&gt;A particularly useful application of MCP was for &lt;strong&gt;presence detection&lt;/strong&gt;. Here's how the system behaves with MCP:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When motion is detected while the context indicates that the house is "empty", the context updates &lt;code&gt;presence&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The camera module, upon detecting &lt;code&gt;presence: true&lt;/code&gt;, automatically stops recording to respect privacy.&lt;/li&gt;
&lt;li&gt;The lighting controller turns on lights only if &lt;code&gt;presence: true&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;is_dark: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Context updates are logged and stored for historical analysis or future machine learning applications.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Benefits of Using MCP
&lt;/h2&gt;

&lt;p&gt;Implementing MCP brought several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved consistency&lt;/strong&gt;: All agents operate based on a unified context model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced false triggers&lt;/strong&gt;: Actions are taken only when multiple conditions are satisfied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier debugging&lt;/strong&gt;: Centralized logging of context changes makes it easier to trace issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt;: New agents or logic can be added without modifying existing ones, as long as they follow the context protocol.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security and Reliability Considerations
&lt;/h2&gt;

&lt;p&gt;While this system is not exposed to the public internet, I added basic measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token-based authentication for WebSocket clients&lt;/li&gt;
&lt;li&gt;Input validation using &lt;code&gt;jsonschema&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Context versioning and timestamp tracking&lt;/li&gt;
&lt;li&gt;Manual override controls via command line interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future improvements may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS encryption for WebSocket communication&lt;/li&gt;
&lt;li&gt;Integration with a dashboard for visual monitoring&lt;/li&gt;
&lt;li&gt;Rule-based automation engine using YAML or JSON configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a smart home system that behaves intelligently requires more than just connecting devices. The key lies in providing a &lt;strong&gt;shared understanding of context&lt;/strong&gt; that all devices can refer to.&lt;/p&gt;

&lt;p&gt;By implementing a simple Model Context Protocol, I was able to transform a fragile collection of scripts into a robust and extensible system where each component plays its part based on the global state.&lt;/p&gt;

&lt;p&gt;This approach is scalable, reusable, and applicable well beyond home automation—any distributed system can benefit from having a centralized or decentralized context synchronization strategy.&lt;/p&gt;

&lt;p&gt;If you're interested in the source code for the context server or the agents, feel free to reach out. I’d be happy to publish it in a follow-up article.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Getting Started with Podman: My First Splunk Test Lab</title>
      <dc:creator>Andrew Raieta</dc:creator>
      <pubDate>Fri, 13 Jun 2025 10:50:39 +0000</pubDate>
      <link>https://dev.to/miwaty/getting-started-with-podman-my-first-splunk-test-lab-4ceg</link>
      <guid>https://dev.to/miwaty/getting-started-with-podman-my-first-splunk-test-lab-4ceg</guid>
      <description>&lt;p&gt;A few weeks ago, I stumbled upon a LinkedIn post that mentioned Podman as a drop-in replacement for Docker—daemonless, rootless, and open-source. I had heard about it before but never gave it much thought. This time, the post got my attention.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: I'm not a professional writer or seasoned blogger.&lt;br&gt;
I mostly use Dev.to as a notebook or public library for my tech experiments.&lt;br&gt;
That said—I genuinely hope you’ll find something useful here that helps you replicate, improve, or build your own version of this small Splunk lab with Podman.&lt;br&gt;
If you have suggestions, I’m always open to learning more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm working a lot with Splunk, and I often spin up quick labs to test different components like Indexers, Heavy Forwarders, and Search Heads. I figured—why not try doing this with Podman?&lt;/p&gt;

&lt;p&gt;Here’s how I set up a basic Splunk architecture using Podman and podman-compose.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the podman-compose.yml&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;**&lt;br&gt;
I used a file nearly identical to what I’d write for Docker Compose, since podman-compose is compatible with the Compose Specification.&lt;br&gt;
**&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.8'

services:
  idx:
    image: docker.io/splunk/splunk:latest
    container_name: idx
    environment:
      - SPLUNK_START_ARGS=--accept-license
      - SPLUNK_PASSWORD=Splunk@00
      - SPLUNK_ROLE=splunk_indexer
      - SPLUNK_ENABLE_LISTEN=9997
    ports:
      - "8000:8000"
      - "9997:9997"
      - "8089:8089"
    networks:
      - splunk-net

  hf:
    image: docker.io/splunk/splunk:latest
    container_name: hf
    environment:
      - SPLUNK_START_ARGS=--accept-license
      - SPLUNK_PASSWORD=Splunk@00
      - SPLUNK_ROLE=splunk_heavy_forwarder
    ports:
      - "8001:8000"
    networks:
      - splunk-net
    depends_on:
      - idx

  sh:
    image: docker.io/splunk/splunk:latest
    container_name: sh
    environment:
      - SPLUNK_START_ARGS=--accept-license
      - SPLUNK_PASSWORD=Splunk@00
      - SPLUNK_ROLE=splunk_search_head
    ports:
      - "8003:8000"
    networks:
      - splunk-net
    depends_on:
      - idx

networks:
  splunk-net:
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run It with podman-compose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;podman-compose -f podman-compose.yml up -d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This brought up the three containers: idx, hf, and sh, running on the same network.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Post-Startup Configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Search Head
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;podman exec -u splunk -it sh bash
/opt/splunk/bin/splunk add search-server idx:8089 -remoteUsername admin -remotePassword Splunk@00 -auth admin:Splunk@00
exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Heavy Forwarder
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;podman exec -u splunk -it hf bash
/opt/splunk/bin/splunk add forward-server idx:9997 -auth admin:Splunk@00
exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Verify the Configuration&lt;br&gt;
Go to Splunk Web Access:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the Search Head, go to&lt;br&gt;
  Settings &amp;gt; Distributed Search &amp;gt; Search Peers&lt;br&gt;
  and verify that the indexer appears and is connected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the Heavy Forwarder, check&lt;br&gt;
  Settings &amp;gt; Forwarding and receiving &amp;gt; Forwarded Data&lt;br&gt;
  to confirm that data is being forwarded to the indexer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Honestly, I didn’t expect Podman to work this smoothly. The only real change I had to make was adding the full image path (docker.io/splunk/splunk) to avoid name resolution issues. Otherwise, the experience felt familiar and lightweight.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>have fun with Zeek</title>
      <dc:creator>Andrew Raieta</dc:creator>
      <pubDate>Sun, 16 Mar 2025 21:34:18 +0000</pubDate>
      <link>https://dev.to/miwaty/have-fun-with-zeek-4c5</link>
      <guid>https://dev.to/miwaty/have-fun-with-zeek-4c5</guid>
      <description>&lt;h2&gt;
  
  
  What a wonderful tool zeek is!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is the thought I had after realising the versatility and potential of the instrument.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A network security monitoring tool, I needed this to perform a task. Its extensibility through the installation of protocol recognition packages is what impressed me the most, but let's take two steps back and start from the beginning.&lt;/p&gt;

&lt;p&gt;I am using Zeek on my Raspberry(&lt;strong&gt;ubuntu&lt;/strong&gt;), connected to a switch port(configured in monitor mode), Zeek operating to analyse and catalogue traffic, and a Universal Forwarder to make my life easier for post-capture data analysis.&lt;/p&gt;

&lt;p&gt;Once the traffic capture begins, the captured packets are sent to an Event engine which is able to transform the flow of packets into a series of events representing network activity in a natural form. For example, an event of type HTTP is converted into its equivalent to know the ip addresses involved, http version used and the uri involved. Likewise an SSH type event contains the authentication, the IPs involved and the status. All that is extracted and converted is thanks to scripts already included in Zeek, which already allow a lot of information to be obtained with just the basis of the tool. All this is carefully placed in .log files for reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are all types of protocols recognised?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The answer is yes.&lt;/strong&gt;&lt;br&gt;
but&lt;br&gt;
But if we install the appropriate plugins, which I will explain in detail in another post. I had to perform an assessment discovery to compile a list of everything on that network segmentation.&lt;/p&gt;

&lt;p&gt;Then knowing the domain context (small company) i.e. a small 3D printing company, I could have made a list by hand and then checked Zeek's results against my list. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But that was no fun!!!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So what I did was ask them to mirror a port and thus start Zeek. &lt;br&gt;
Zeek fills a file called conn.log with all captured IP addresses primarily, located in the logs/current/ directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main Log: conn.log&lt;/strong&gt;&lt;br&gt;
This file records all observed network connections, including:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source IP (id.orig_h)&lt;br&gt;
Destination IP (id.resp_h)&lt;br&gt;
Ports, protocol, connection duration, and data transferred&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ts uid id.orig_h id.resp_h proto service
1693401923.01 C5mAqR1T7kl 192.168.1.10 8.8.8 udp dns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a full list of observed IPs in Zeek's network monitoring.&lt;/p&gt;

&lt;p&gt;Then from there knowing the context and also recognising some services I was able to do some research and be able to install the exact modules (&lt;a href="https://packages.zeek.org/" rel="noopener noreferrer"&gt;zeek-packages&lt;/a&gt;). One of these was ICSNPP-Modbus. Making a second round I completed the list and checking the parameters I got a good &lt;strong&gt;90%&lt;/strong&gt; coverage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What did I learn from this experience?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Definitely a new tool to study in depth. A little more about the world of PLCs.&lt;br&gt;
The architecture of Zeek itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can you experiment?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Surely if you have a knowledge that allows you to monitor a network legally, you can try anything and for as long as you want since scanning is passive you do not risk doing damage or crashing networks.&lt;/p&gt;

&lt;p&gt;Otherwise if you do not have the possibility of having a small infrastructure at home, you can always analyze pcap packets!!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small Example:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take a pcap of your interest, I recommend this list:&lt;br&gt;
&lt;a href="https://github.com/automayt/ICS-pcap" rel="noopener noreferrer"&gt;ICS-pcap&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run the command:

**zeek -r &amp;lt;pcap&amp;gt;**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and at the end of the procedure you will have your files with the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to analyze files more comfortably?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I use Splunk, it allows me to gather all the files under an index and launch SPL queries to facilitate the analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some tips&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  - Perform a diagnosis with zeekctl check
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When installing modules never skip the tests&lt;/li&gt;
&lt;li&gt;Do not install zkg as an external module with apt, but use the one under the --/zeek/bin folder&lt;/li&gt;
&lt;li&gt;Contact me if you have errors.&lt;/li&gt;
&lt;li&gt;Arm yourself with patience and remember that you must have in mind the infrastructure on which you are operating with Zeek&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>zeek</category>
      <category>wireless</category>
    </item>
  </channel>
</rss>
