[{"content":"What This Project Is I built a backend that scrapes websites, stores data in PostgreSQL and MongoDB, and exposes REST APIs through FastAPI and Flask. I made it as a portfolio piece — something I could demo and actually run on my machine.\n1. What Does This Project Do? (Simple Explanation) This project is a data aggregator backend.\nIt can:\nScrape product or article data from websites Store that data in two databases (PostgreSQL for structured data, MongoDB for flexible logs) Expose REST APIs so other apps can create, read, update, and delete data Call external APIs and return the results You can think of it as:\nA small backend that collects data from the web, saves it, and lets you access it through simple HTTP endpoints.\n2. Technologies Used Technology Purpose FastAPI Main REST API – fast, modern, auto-generates docs Flask Second API for data processing and proxying BeautifulSoup Scrape static HTML pages (no browser needed) Selenium Scrape JavaScript-heavy pages (uses headless Chrome) PostgreSQL Relational database for structured product data MongoDB Document database for flexible scrape logs SQLAlchemy Connect to PostgreSQL and run queries PyMongo Connect to MongoDB and store documents 3. What You Need Before Starting Make sure you have:\nPython 3.11 or 3.12 (recommended — some packages may not support the newest Python yet) Docker and Docker Compose (for PostgreSQL and MongoDB) Git (for version control) You do not need to install PostgreSQL or MongoDB on your system. Docker will run them in containers.\n4. Project Structure (Overview) After setup, your project will look like this:\nbackend/ ├── app/ # FastAPI main application │ ├── main.py # All REST endpoints │ ├── schemas.py # Data validation (Pydantic) │ └── crud.py # Database operations ├── flask_service/ # Flask microservice │ └── app.py # Text processing, proxy endpoints ├── scrapers/ # Web scraping code │ ├── beautifulsoup_scraper.py │ ├── selenium_scraper.py │ └── demo_scraper.py ├── db/ # Database setup │ ├── postgres.py # PostgreSQL connection │ ├── mongodb.py # MongoDB connection │ ├── models.py # Table definitions │ └── init_db.py # Create tables ├── config.py # Settings (env variables) ├── docker-compose.yml # PostgreSQL + MongoDB ├── requirements.txt # Python packages └── README.md 5. Step 1: Create a Virtual Environment A virtual environment keeps your project’s packages separate from the rest of your system.\ncd ~/Desktop/backend python -m venv venv Activate it:\n# On Linux or Mac: source venv/bin/activate # On Windows: venv\\Scripts\\activate You should see (venv) at the start of your terminal line. That means the virtual environment is active.\n6. Step 2: Install Dependencies Install all required Python packages:\npip install -r requirements.txt This installs FastAPI, Flask, BeautifulSoup, Selenium, SQLAlchemy, PyMongo, and other libraries.\nIf you get errors with psycopg2 or pydantic, try using Python 3.11 or 3.12. Some packages may not fully support the newest Python yet.\n7. Step 3: Start the Databases with Docker PostgreSQL and MongoDB run inside Docker containers. You do not need to install them on your computer.\nStart both databases:\ndocker-compose up -d The -d flag runs them in the background. You should see something like:\nContainer backend-postgres-1 Started Container backend-mongodb-1 Started Check that they are running:\ndocker ps You should see two containers: one for PostgreSQL (port 5432) and one for MongoDB (port 27017).\n8. Step 4: Create the Database Tables PostgreSQL needs tables before it can store data. Run the init script:\npython -m db.init_db You should see:\nPostgreSQL tables created successfully! This creates a products table with columns for title, price, description, and source URL.\n9. Step 5: Run the FastAPI Server Start the main API:\nuvicorn app.main:app --reload --port 8000 The --reload flag restarts the server when you change the code. Good for development.\nYou should see:\nUvicorn running on http://127.0.0.1:8000 Application startup complete. Open your browser and go to:\nhttp://localhost:8000 You will see a welcome message with links to the API docs and endpoints.\n10. Step 6: Open the Interactive API Docs FastAPI automatically generates documentation. Go to:\nhttp://localhost:8000/docs This is the Swagger UI. You can:\nSee all endpoints (GET, POST, PUT, DELETE) Try them out directly in the browser Send requests and see responses No need to use curl or Postman while learning. The docs page does it for you.\n11. Step 7: Try the Scraping Endpoint The project includes a demo scraper that uses a practice website called books.toscrape.com. It does not need Chrome or Selenium.\nIn the Swagger UI at /docs:\nFind POST /scrape Click Try it out Leave the body as {} (empty JSON) Click Execute The API will:\nScrape book titles and prices from the demo site Save them in PostgreSQL Log the scrape in MongoDB Then open GET /products and click Try it out → Execute. You will see the scraped products.\n12. Step 8: Run the Flask Service (Optional) The Flask service is a small second API. It can process text and proxy requests to the FastAPI backend.\nOpen a new terminal (keep FastAPI running in the first one). Then:\ncd ~/Desktop/backend source venv/bin/activate python flask_service/app.py Flask will run on port 5000. Open:\nhttp://localhost:5000 You will see a list of Flask endpoints. For example, POST /process/text lets you send text and get back word count and character count.\n13. Understanding the Two Scraping Tools BeautifulSoup (Static HTML) Use when: The website’s content is already in the HTML when the page loads. No JavaScript needed to show the data.\nPros: Fast, simple, no browser required Cons: Cannot handle content loaded by JavaScript Example: books.toscrape.com, many news sites, simple product pages\nSelenium (Dynamic Pages) Use when: The website uses JavaScript to load content. The data appears only after the page runs scripts.\nPros: Can handle complex, dynamic sites Cons: Slower, needs Chrome/Chromium and chromedriver Example: Single-page apps (SPAs), sites that load data via AJAX\n14. Why Two Databases? (PostgreSQL and MongoDB) PostgreSQL (Relational) Structured data with a fixed schema (columns: title, price, etc.) ACID – reliable transactions Good for: Products, users, orders – anything with a clear structure MongoDB (Document Store) Flexible data – each document can have different fields Good for: Logs, raw scrape results, data that changes shape No fixed schema – you can add new fields anytime In this project:\nPostgreSQL stores products (title, price, description) MongoDB stores scrape logs (source URL, count, raw product list) 15. Main API Endpoints (Quick Reference) FastAPI (Port 8000) Method Endpoint What It Does GET / Welcome message GET /health Health check GET /products List all products GET /products/{id} Get one product POST /products Create a product PUT /products/{id} Update a product DELETE /products/{id} Delete a product POST /scrape Run the scraping pipeline GET /external/posts Fetch posts from JSONPlaceholder GET /scrape-logs View MongoDB scrape logs Flask (Port 5000) Method Endpoint What It Does GET / Welcome message GET /health Health check POST /process/text Process text (word count, etc.) GET /proxy/products Get products from FastAPI GET /proxy/external-posts Get external posts via FastAPI 16. Example: Create a Product with curl From the terminal:\ncurl -X POST http://localhost:8000/products \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;title\u0026#34;:\u0026#34;My Product\u0026#34;,\u0026#34;price\u0026#34;:19.99,\u0026#34;description\u0026#34;:\u0026#34;A test item\u0026#34;}\u0026#39; You will get back the created product with an ID. Then list all products:\ncurl http://localhost:8000/products 17. Example: Trigger a Scrape with a Custom URL You can scrape any URL. Send a POST request:\ncurl -X POST http://localhost:8000/scrape \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;url\u0026#34;:\u0026#34;https://example.com\u0026#34;,\u0026#34;use_selenium\u0026#34;:false}\u0026#39; url: The website to scrape use_selenium: true for JavaScript-heavy sites, false for static HTML (BeautifulSoup) 18. Useful Commands (Summary) Task Command Start databases docker-compose up -d Stop databases docker-compose down Create tables python -m db.init_db Run FastAPI uvicorn app.main:app --reload --port 8000 Run Flask python flask_service/app.py API docs Open http://localhost:8000/docs 19. What You Learned (Summary) You now know how to:\nBuild RESTful APIs with FastAPI and Flask Scrape websites with BeautifulSoup (static) and Selenium (dynamic) Use PostgreSQL for structured data and MongoDB for flexible logs Run PostgreSQL and MongoDB with Docker Perform CRUD operations (Create, Read, Update, Delete) Integrate with external APIs (e.g. JSONPlaceholder) Push the project to GitHub This project demonstrates skills I wanted on my resume: REST design, web scraping, picking the right database, and wiring up external APIs.\n","permalink":"https://ayyzenn.github.io/posts/restful-api-web-scraping-pipelines/","summary":"\u003ch2 id=\"what-this-project-is\"\u003eWhat This Project Is\u003c/h2\u003e\n\u003cp\u003eI built a backend that scrapes websites, stores data in PostgreSQL and MongoDB, and exposes REST APIs through FastAPI and Flask. I made it as a portfolio piece — something I could demo and actually run on my machine.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-what-does-this-project-do-simple-explanation\"\u003e1. What Does This Project Do? (Simple Explanation)\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eThis project is a data aggregator backend.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eIt can:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eScrape\u003c/strong\u003e product or article data from websites\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStore\u003c/strong\u003e that data in two databases (PostgreSQL for structured data, MongoDB for flexible logs)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eExpose REST APIs\u003c/strong\u003e so other apps can create, read, update, and delete data\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCall external APIs\u003c/strong\u003e and return the results\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou can think of it as:\u003c/p\u003e","title":"RESTful API with Web Scraping: My FastAPI + Flask Project"},{"content":"What I Built I wanted Jenkins to pull code from GitHub, build a Docker image, and run a container automatically. This post is the pipeline I put together for that.\nGoal: From Manual to Automatic Manually, we usually do this:\nClone a GitHub repository Run docker build Run docker run Check the output With Jenkins, all of this happens automatically when the pipeline runs.\nYou click Build Now (or trigger from GitHub), and Jenkins does the rest.\nPrerequisites (Short Version) You should already have:\nJenkins installed and working Docker installed and working Jenkins user allowed to run Docker (for example, by adding the jenkins user to the docker group) On many systems this looks like:\nUbuntu (after adding your user to the docker group):\nsudo usermod -aG docker jenkins sudo systemctl restart jenkins Arch Linux:\nsudo usermod -aG docker jenkins sudo systemctl restart jenkins Then log out and log back in, and make sure a simple docker ps works from the Jenkins environment.\nRepository Structure My GitHub repository looks like this:\njenkins-pipeline-demo/ ├── Jenkinsfile ├── Dockerfile └── app.sh Each file has a clear role:\nJenkinsfile → Jenkins pipeline steps Dockerfile → How the Docker image is built app.sh → Script that runs inside the container app.sh: The Script Inside the Container This is a simple shell script that prints some information. It proves that the container actually runs.\n#!/bin/sh echo \u0026#34;Hello from GitHub + Jenkins Pipeline\u0026#34; uname -a date Save this as app.sh and make it executable:\nchmod +x app.sh Dockerfile: How the Image Is Built This Dockerfile builds a small image using Alpine Linux:\nFROM alpine:latest WORKDIR /app COPY app.sh . RUN chmod +x app.sh CMD [\u0026#34;./app.sh\u0026#34;] Explanation:\nUses Alpine Linux, which is small and fast Copies app.sh into /app Makes it executable Runs ./app.sh when the container starts If you build and run this locally, you should see the same output that Jenkins will show later:\ndocker build -t jenkins-demo-app . docker run --rm jenkins-demo-app Jenkinsfile: The Pipeline Definition This is the Jenkins declarative pipeline stored in the repo:\npipeline { agent any stages { stage(\u0026#39;Checkout\u0026#39;) { steps { echo \u0026#39;Code checked out from GitHub\u0026#39; } } stage(\u0026#39;Build Docker Image\u0026#39;) { steps { sh \u0026#39;docker build -t jenkins-demo-app .\u0026#39; } } stage(\u0026#39;Run Docker Container\u0026#39;) { steps { sh \u0026#39;\u0026#39;\u0026#39; docker rm -f jenkins-demo-container || true docker run --name jenkins-demo-container jenkins-demo-app \u0026#39;\u0026#39;\u0026#39; } } } post { always { echo \u0026#39;Cleaning up...\u0026#39; sh \u0026#39;docker ps -a\u0026#39; } } } What This Pipeline Does Jenkins automatically checks out code from GitHub (handled by Jenkins when using SCM) Builds a Docker image named jenkins-demo-app Removes any old container with the same name (if it exists) Runs a new container named jenkins-demo-container After the run, prints docker ps -a so you can see container status Put the Code on GitHub In your local folder:\ngit init git add Jenkinsfile Dockerfile app.sh git commit -m \u0026#34;Jenkins Docker pipeline demo\u0026#34; git branch -M main git remote add origin https://github.com/your-username/jenkins-pipeline-demo.git git push -u origin main Now your GitHub repo has:\nJenkinsfile Dockerfile app.sh Create the Jenkins Pipeline Job In Jenkins:\nGo to New Item Name: docker-pipeline-demo Type: Pipeline Click OK Scroll down to the Pipeline section and set:\nDefinition: Pipeline script from SCM SCM: Git Repository URL: your GitHub repo, for example\nhttps://github.com/your-username/jenkins-pipeline-demo.git Credentials: (optional) your GitHub token, if the repo is private Branch Specifier: */main Script Path: Jenkinsfile Click Save.\nNow Jenkins knows where to pull code from and which Jenkinsfile to use.\nRun the Pipeline and Check Output From the docker-pipeline-demo job page:\nClick Build Now Click the build number (for example #1) Click Console Output In the log you should see:\nGit repository being cloned Docker image being built Docker container being started Output from app.sh, like: Hello from GitHub + Jenkins Pipeline Linux ... Mon Feb 10 12:34:56 UTC 2026 If there are Docker permission errors, check:\nJenkins user in docker group Docker daemon is running Why This Setup Is Important This pipeline is useful because:\nNo manual Docker commands: Jenkins runs them for you Same build process every time: reproducible builds Easy to extend: you can add tests, security scans, or deployments Works well with GitHub: every change in Git can trigger the same pipeline This is the base of many real CI/CD systems.\nWhat You Can Add Next From here, you can extend the pipeline to:\nPush the image to Docker Hub or another registry Use Docker Compose to run multiple services Add test stages before building or after running the container Deploy to a server, VM, or Kubernetes Trigger builds using GitHub webhooks Conclusion Using Jenkins with Docker and GitHub makes automation simple and reliable.\nOnce this base pipeline works, you can slowly turn it into a production-grade CI/CD pipeline:\nSame idea Same tools Just more stages and checks This setup is small, but very powerful.\n","permalink":"https://ayyzenn.github.io/posts/docker-pipeline-from-github/","summary":"\u003ch2 id=\"what-i-built\"\u003eWhat I Built\u003c/h2\u003e\n\u003cp\u003eI wanted Jenkins to pull code from GitHub, build a Docker image, and run a container automatically. This post is the pipeline I put together for that.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"goal-from-manual-to-automatic\"\u003eGoal: From Manual to Automatic\u003c/h2\u003e\n\u003cp\u003eManually, we usually do this:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eClone a GitHub repository\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003edocker build\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003edocker run\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eCheck the output\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eWith Jenkins, all of this happens \u003cstrong\u003eautomatically\u003c/strong\u003e when the pipeline runs.\u003c/p\u003e\n\u003cp\u003eYou click \u003cstrong\u003eBuild Now\u003c/strong\u003e (or trigger from GitHub), and Jenkins does the rest.\u003c/p\u003e","title":"Jenkins + Docker Pipeline from GitHub"},{"content":"Why I Wrote This I run Arch Linux with i3 and wanted Jenkins locally for CI/CD experiments. This covers Java 21, the Jenkins install, a test job, and a GitHub pipeline — exactly what I ran on my machine.\n1. What is Jenkins? (Simple Explanation) Jenkins is an automation server.\nYou can tell Jenkins to:\nGet your code (for example from GitHub) Run commands (build, test, scripts) Show you if things passed or failed Instead of you running commands manually every time, Jenkins does it automatically.\nYou can think of Jenkins as:\nA small robot that runs your scripts in a safe and repeatable way.\n2. Java Requirement (Very Important) Jenkins is a Java application.\nIt needs a stable, Long-Term Support (LTS) version of Java.\nRecommended Java Versions Java Version Type Recommendation Java 17 LTS Stable — works well Java 21 LTS Best choice Java 25 Non-LTS Not recommended In this guide we use Java 21 (LTS) on Arch.\n3. Remove Wrong Java Version (If Needed) First, check which Java version you have:\njava -version If you see something like Java 25 or a non‑LTS version, remove it:\n# Remove the current OpenJDK package (example: jdk-openjdk) sudo pacman -Rns jdk-openjdk # Optionally clean unused dependencies sudo pacman -Rns $(pacman -Qtdq) If you already have Java 21 installed, you can skip removal.\n4. Install Java 21 (LTS) on Arch Install Java 21 JDK:\nsudo pacman -S jdk21-openjdk Verify the version:\njava -version You should see something like:\nopenjdk version \u0026#34;21.x.x\u0026#34; 20xx-xx-xx If the version shows 21, you are good.\n5. Install Jenkins on Arch Linux Arch Linux has a jenkins package.\nInstall it:\nsudo pacman -S jenkins During installation you may see something like:\nThere are 2 providers available for java-runtime=21: 1) jdk21-openjdk 2) jre21-openjdk Choose:\n1 This means Jenkins will use the JDK 21 runtime.\n6. Start and Enable Jenkins Service Start Jenkins now:\nsudo systemctl start jenkins Enable Jenkins to start on boot:\nsudo systemctl enable jenkins Check that it is running:\nsystemctl status jenkins You should see something like:\nActive: active (running) If it is not active, read the last few lines of the status output for errors.\n7. Open Jenkins in Your Browser By default Jenkins listens on port 8080.\nOpen your browser and go to:\nhttp://localhost:8080 You should see the Jenkins unlock screen.\nIf you cannot open it:\nCheck that Jenkins is running: systemctl status jenkins Check firewall rules (if you use one) 8. Get the Jenkins Unlock Password On the first run, Jenkins asks for an initial admin password.\nGet it with:\nsudo cat /var/lib/jenkins/secrets/initialAdminPassword Copy the long string and paste it into the browser field.\nClick Continue.\n9. Jenkins Initial Setup After unlock, Jenkins will ask you how to set it up.\nClick Install suggested plugins Wait until plugins finish installing Create your admin user (username, password, email) Click Save and Finish Click Start using Jenkins Now you are on the main Jenkins Dashboard.\n10. Create a Simple Freestyle Job We will create a tiny job that prints some information.\nThis proves Jenkins is working correctly.\n10.1 Create the Job On the Jenkins dashboard, click New Item In Item name, type: hello-jenkins Select Freestyle project Click OK You are now in the job configuration page.\n10.2 Add a Build Step Scroll down to the Build section.\nClick Add build step → Execute shell In the Command box, paste: echo \u0026#34;Hello Jenkins!\u0026#34; echo \u0026#34;Running on Arch Linux\u0026#34; date whoami Click Save 11. Run the Freestyle Job From the job page for hello-jenkins:\nClick Build Now On the left side, you will see a build number, for example #1 Click on #1 Click Console Output You should see output similar to:\nHello Jenkins! Running on Arch Linux Tue Feb 10 13:00:00 PKT 2026 jenkins If you see this, your first Jenkins job is working correctly.\n12. Where Jenkins Stores Its Files (Useful Paths) Jenkins uses these main paths on Arch:\nPurpose Path Jenkins home /var/lib/jenkins Jobs /var/lib/jenkins/jobs/ Job workspace /var/lib/jenkins/workspace/ Config /etc/conf.d/jenkins Logs /var/log/jenkins/ For example, to see files for your job:\nls /var/lib/jenkins/jobs/hello-jenkins/ 13. (Optional) When Jenkins Uses a Git Repo When Jenkins checks out a Git repository for a job, it places it in the workspace folder:\n/var/lib/jenkins/workspace/\u0026lt;job-name\u0026gt;/ Example for our later pipeline job:\n/var/lib/jenkins/workspace/github-pipeline/ This is useful when you want to debug or inspect the code that was built.\n14. Common Jenkins Service Commands Some handy commands for daily use:\n# Restart Jenkins (after config changes) sudo systemctl restart jenkins # Stop Jenkins sudo systemctl stop jenkins # Start Jenkins sudo systemctl start jenkins # Follow Jenkins logs in real-time journalctl -u jenkins -f 15. Jenkins Pipeline + GitHub (Realistic Example) Now we will set up a real Jenkins Pipeline that:\nUses a GitHub repository Stores the pipeline in a Jenkinsfile inside the repo Lets Jenkins pull and run the pipeline automatically This is how Jenkins is used in real projects.\n15.1 What We Will Build On GitHub we will have:\njenkins-pipeline-demo ├── Jenkinsfile └── app.sh Jenkins will:\nClone the repo Read the Jenkinsfile Run the script app.sh as part of the pipeline 16. Install Git (If Not Already Installed) On Arch:\nsudo pacman -S git Check Git:\ngit --version If you see a version number, Git is installed correctly.\n17. Create the GitHub Repository Go to GitHub and log in Click New repository Name it: jenkins-pipeline-demo Choose Public (easier while learning) Do not initialize with a README Click Create repository Copy the repo URL, for example:\nhttps://github.com/ayyzenn/jenkins-pipeline-demo.git 18. Prepare the Repo Locally In your terminal:\ncd ~ mkdir jenkins-pipeline-demo cd jenkins-pipeline-demo git init 18.1 Create a Simple Script Create a small script file:\nnano app.sh Paste:\n#!/bin/bash echo \u0026#34;Hello from GitHub + Jenkins Pipeline\u0026#34; uname -a date Save and exit. Then make it executable:\nchmod +x app.sh 19. Create the Jenkinsfile (Important) Now create a Jenkinsfile in the same folder:\nnano Jenkinsfile Paste this pipeline definition:\npipeline { agent any stages { stage(\u0026#39;Clone\u0026#39;) { steps { echo \u0026#39;Repository cloned by Jenkins\u0026#39; } } stage(\u0026#39;Run Script\u0026#39;) { steps { sh \u0026#39;./app.sh\u0026#39; } } } post { success { echo \u0026#39;Pipeline finished successfully\u0026#39; } failure { echo \u0026#39;Pipeline failed\u0026#39; } } } Save and exit.\nThis file tells Jenkins what to do when the pipeline runs.\n20. Push the Code to GitHub Back in the jenkins-pipeline-demo folder:\ngit add . git commit -m \u0026#34;Initial Jenkins pipeline\u0026#34; git branch -M main git remote add origin https://github.com/ayyzenn/jenkins-pipeline-demo.git git push -u origin main Now GitHub has:\napp.sh Jenkinsfile 21. Create a GitHub Token for Jenkins Jenkins needs permission to pull from your private or API-protected repos.\nOn GitHub, go to Settings → Developer settings → Personal access tokens Create a new token (classic or fine-grained) Give it repo access Copy the token (keep it secret) You will use this token as a password in Jenkins credentials.\n22. Add GitHub Credentials to Jenkins In Jenkins:\nGo to Manage Jenkins → Credentials Select a (global) domain (or create one) Click Add Credentials Fill in:\nKind: Username with password Username: your GitHub username Password: the GitHub token you created ID: github-token (easy to remember) Click OK / Save.\n23. Create a Jenkins Pipeline Job (From SCM) Back in the Jenkins dashboard:\nClick New Item Name it: github-pipeline Select Pipeline Click OK You are now in the job configuration.\n24. Configure the Pipeline (From Git) Scroll down to the Pipeline section.\nSet:\nDefinition: Pipeline script from SCM SCM: Git Then fill in:\nRepository URL:\nhttps://github.com/ayyzenn/jenkins-pipeline-demo.git Credentials: select github-token\nBranch Specifier:\n*/main Script Path:\nJenkinsfile Click Save.\nNow Jenkins knows:\nWhich repo to clone Which branch to use Which file (Jenkinsfile) describes the pipeline 25. Run the Jenkins Pipeline From the github-pipeline job page:\nClick Build Now Click the build number (for example #1) Click Console Output You should see logs similar to:\nCloning the remote Git repository Repository cloned by Jenkins [Pipeline] sh + ./app.sh Hello from GitHub + Jenkins Pipeline ... (system info and date) ... Pipeline finished successfully Finished: SUCCESS If you see Finished: SUCCESS, your GitHub-based Jenkins Pipeline is working.\n26. What You Learned (Summary) You now know how to:\nInstall Java 21 and Jenkins on Arch Linux Start and manage the jenkins systemd service Create a freestyle job that runs shell commands Understand where Jenkins stores its jobs and workspaces Put your pipeline definition in a GitHub repo as a Jenkinsfile Create a Jenkins Pipeline job that reads from SCM (Git) This is the same pattern teams use in real projects:\nCode + Jenkinsfile live in Git Jenkins pulls the repo Jenkins runs the pipeline defined in the repo ","permalink":"https://ayyzenn.github.io/posts/jenkins-on-arch-linux/","summary":"\u003ch2 id=\"why-i-wrote-this\"\u003eWhy I Wrote This\u003c/h2\u003e\n\u003cp\u003eI run Arch Linux with i3 and wanted Jenkins locally for CI/CD experiments. This covers Java 21, the Jenkins install, a test job, and a GitHub pipeline — exactly what I ran on my machine.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-what-is-jenkins-simple-explanation\"\u003e1. What is Jenkins? (Simple Explanation)\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eJenkins is an automation server.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eYou can tell Jenkins to:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGet your code (for example from GitHub)\u003c/li\u003e\n\u003cli\u003eRun commands (build, test, scripts)\u003c/li\u003e\n\u003cli\u003eShow you if things passed or failed\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eInstead of you running commands manually every time, Jenkins does it \u003cstrong\u003eautomatically\u003c/strong\u003e.\u003c/p\u003e","title":"Jenkins on Arch Linux: How I Set It Up on i3"},{"content":"Why This Post Exists After writing my first real Ansible playbook, I realized something:\nInstalling software manually once is easy. Installing it correctly, repeatedly, and safely is not.\nSo I decided to automate something real and useful:\nInstall Docker Engine (official way) Prepare an Ubuntu server to become a Kubernetes node Do everything using Ansible No SSH-ing into the server again and again This post documents that journey.\nReal Setup (Same as Before) Nothing fancy. Just real machines.\nRole OS Control Node Arch Linux (i3 WM) Managed Node Ubuntu 22.04 Tool Ansible Goal Kubernetes-ready node Yes, Arch → Ubuntu automation works perfectly fine.\nProject Structure (Clean \u0026amp; Grown-Up) Once things get bigger, single playbooks get messy. So I moved to roles.\nansible-k8s-bootstrap/ ├── inventory.ini ├── playbook.yml └── roles/ ├── docker/ │ ├── tasks/main.yml │ ├── handlers/main.yml │ └── vars/main.yml └── k8s_node/ ├── tasks/main.yml ├── handlers/main.yml └── vars/main.yml Think of roles as:\n\u0026ldquo;One role = one responsibility\u0026rdquo;\nInventory (Nothing New Here) [ubuntu_servers] 192.168.1.80 ansible_user=ubuntu ansible_python_interpreter=/usr/bin/python3 Same server, same rules.\nPhase 1: Installing Docker (The Right Way) Why Not apt install docker.io? Because Kubernetes is picky.\nUbuntu\u0026rsquo;s default Docker package:\nIs often outdated Can break container runtimes Causes weird kubelet issues later So we install Docker from Docker\u0026rsquo;s official repository.\nDocker Variables (No Hardcoding) roles/docker/vars/main.yml:\ndocker_apt_packages: - ca-certificates - curl - gnupg - lsb-release docker_packages: - docker-ce - docker-ce-cli - containerd.io - docker-buildx-plugin - docker-compose-plugin docker_gpg_key_path: /etc/apt/keyrings/docker.gpg Why variables?\nBecause future-you will thank you.\nDocker Tasks (What Ansible Actually Does) roles/docker/tasks/main.yml:\n- name: Install prerequisite packages apt: name: \u0026#34;{{ docker_apt_packages }}\u0026#34; state: present update_cache: yes - name: Create keyrings directory file: path: /etc/apt/keyrings state: directory - name: Add Docker GPG key get_url: url: https://download.docker.com/linux/ubuntu/gpg dest: \u0026#34;{{ docker_gpg_key_path }}\u0026#34; - name: Add Docker repository apt_repository: repo: \u0026gt; deb [arch=amd64 signed-by={{ docker_gpg_key_path }}] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable state: present - name: Install Docker Engine apt: name: \u0026#34;{{ docker_packages }}\u0026#34; state: present update_cache: yes notify: restart docker - name: Enable Docker service: name: docker enabled: yes state: started - name: Add user to docker group user: name: \u0026#34;{{ ansible_user }}\u0026#34; groups: docker append: yes First Taste of Handlers (Important Concept) roles/docker/handlers/main.yml:\n- name: restart docker service: name: docker state: restarted Why handlers matter:\nDocker restarts only if something changes No unnecessary restarts Clean automation Phase 2: Kubernetes Node Bootstrap At this point Docker works. But Kubernetes still won\u0026rsquo;t.\nWhy?\nBecause Kubernetes has rules.\nThings Kubernetes Demands Swap must be disabled The cgroup driver must match what kubelet expects Required kernel networking modules must be loaded containerd must use the systemd cgroup driver kubelet, kubeadm, and kubectl versions must stay pinned (no auto-upgrades) We automate all of this.\nKubernetes Variables roles/k8s_node/vars/main.yml:\nk8s_packages: - kubelet - kubeadm - kubectl Kubernetes Tasks (The Serious Stuff) roles/k8s_node/tasks/main.yml:\n- name: Disable swap immediately command: swapoff -a - name: Disable swap permanently replace: path: /etc/fstab regexp: \u0026#39;^([^#].*swap.*)$\u0026#39; replace: \u0026#39;# \\1\u0026#39; - name: Load kernel modules copy: dest: /etc/modules-load.d/k8s.conf content: | overlay br_netfilter - name: Apply sysctl settings copy: dest: /etc/sysctl.d/k8s.conf content: | net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 - name: Reload sysctl command: sysctl --system - name: Configure containerd for Kubernetes shell: | containerd config default \u0026gt; /etc/containerd/config.toml sed -i \u0026#39;s/SystemdCgroup = false/SystemdCgroup = true/\u0026#39; /etc/containerd/config.toml notify: restart containerd - name: Install Kubernetes tools apt: name: \u0026#34;{{ k8s_packages }}\u0026#34; state: present update_cache: yes - name: Hold Kubernetes packages dpkg_selections: name: \u0026#34;{{ item }}\u0026#34; selection: hold loop: \u0026#34;{{ k8s_packages }}\u0026#34; Kubernetes Handler roles/k8s_node/handlers/main.yml:\n- name: restart containerd service: name: containerd state: restarted Again: restart only if config changes.\nFinal Playbook (One Command to Rule Them All) playbook.yml:\n- name: Kubernetes node bootstrap hosts: ubuntu_servers become: true roles: - docker - k8s_node Run it:\nansible-playbook -i inventory.ini playbook.yml --ask-become-pass Verification (Always Trust, But Verify) docker --version swapoff --show kubeadm version Expected feelings:\nRelief Confidence \u0026ldquo;Okay… this is real DevOps now.\u0026rdquo; What I Learned From This Roles make automation readable Handlers prevent stupid restarts Variables prevent future pain Ansible is not magic — it\u0026rsquo;s discipline Final Thoughts Ansible feels like writing instructions for your future self.\nAnd future-you?\nWill be very thankful you automated this.\nWritten while learning Ansible, breaking things safely, and finally doing it the right way.\n","permalink":"https://ayyzenn.github.io/posts/ansible_adv/","summary":"\u003ch2 id=\"why-this-post-exists\"\u003eWhy This Post Exists\u003c/h2\u003e\n\u003cp\u003eAfter writing my \u003cstrong\u003efirst real Ansible playbook\u003c/strong\u003e, I realized something:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eInstalling software manually once is easy.\nInstalling it \u003cstrong\u003ecorrectly, repeatedly, and safely\u003c/strong\u003e is not.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eSo I decided to automate something \u003cstrong\u003ereal\u003c/strong\u003e and \u003cstrong\u003euseful\u003c/strong\u003e:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eInstall \u003cstrong\u003eDocker Engine (official way)\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003ePrepare an Ubuntu server to become a \u003cstrong\u003eKubernetes node\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eDo everything using \u003cstrong\u003eAnsible\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eNo SSH-ing into the server again and again\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis post documents that journey.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"real-setup-same-as-before\"\u003eReal Setup (Same as Before)\u003c/h2\u003e\n\u003cp\u003eNothing fancy. Just real machines.\u003c/p\u003e","title":"Ansible: Docker and Kubernetes Node Setup"},{"content":"What is Ansible? (In Simple Words) Think of Ansible as a remote control for your servers.\nInstead of:\nSSH into server Run commands manually Forget what you changed Repeat the same steps again and again You tell Ansible:\n“Hey, I want this server to look like this.”\nAnd Ansible makes it happen — again and again, without breaking things.\nThe best part?\nNo agent needed Just SSH Simple YAML files Very human-readable My Setup (Real World) This is exactly what I used:\nRole OS Control Node Arch Linux (i3 WM) Managed Node Ubuntu 22.04 Connection SSH Automation Tool Ansible Yes — control node and managed node can be different OSes. That is normal in DevOps.\nInstall Ansible on the Control Node Arch Linux (control node):\nsudo pacman -S ansible ansible --version Ubuntu (if your control machine runs Ubuntu instead):\nsudo apt update sudo apt install ansible ansible --version The managed Ubuntu server only needs Python and SSH — Ansible installs nothing extra there by default.\nStep 1: Inventory File (Who Do I Control?) Ansible needs to know which server to manage.\nI created inventory.ini:\n[ubuntu_servers] 192.168.1.80 ansible_user=ubuntu ansible_python_interpreter=/usr/bin/python3 In plain English:\nGroup name: ubuntu_servers Server IP: 192.168.1.80 SSH user: ubuntu Python path: /usr/bin/python3 (fixed to avoid warnings) Step 2: First Test — Ping the Server Before doing anything serious, always test connectivity:\nansible ubuntu_servers -i inventory.ini -m ping Output (simplified):\nping: pong That’s Ansible saying:\n“Yep, I can talk to your server.”\nStep 3: Installing Packages (Without Logging In) I installed curl on Ubuntu from my Arch machine:\nansible ubuntu_servers -i inventory.ini \\ -m apt -a \u0026#34;name=curl state=present update_cache=yes\u0026#34; \\ --become --ask-become-pass Why --become?\nBecause installing packages needs sudo. Why --ask-become-pass?\nBecause my Ubuntu user uses a sudo password. Important Concept: changed vs ok This confused me at first, but it’s very important.\nFirst run:\nchanged: true Means:\n“I had to do something.”\nSecond run:\nok: true changed: false Means:\n“Everything is already perfect. Nothing to do.”\nThis is called idempotency (fancy word, simple idea).\nStep 4: My First Real Playbook Ad-hoc commands are fine, but playbooks are the real power.\nHere is my beginner playbook setup.yml:\n- name: Ubuntu beginner setup hosts: ubuntu_servers become: true tasks: - name: Install base packages apt: name: - curl - nginx - htop - git - vim state: present update_cache: yes - name: Remove nano editor apt: name: nano state: absent - name: Ensure nginx is stopped service: name: nginx state: stopped This playbook says:\nInstall useful tools Remove nano (optional — use vim instead) Stop nginx Running the Playbook ansible-playbook -i inventory.ini setup.yml --ask-become-pass First run output (important part):\nchanged=1 Second run output:\nchanged=0 That means:\nPlaybook works State is correct Automation is reliable Verifying Things Manually (Just to Be Sure) Check nano:\nnano --version # command not found Check nginx:\nsystemctl status nginx # inactive (dead) Browser test:\nhttp://192.168.1.80 # not loading (as expected) Everything matched my playbook. That felt really satisfying.\nWhat I Learned (Big Takeaways) Control node OS doesn’t matter\nArch controlling Ubuntu? Totally fine. Managed node OS decides modules\nUbuntu → apt Arch → pacman Idempotency is king\nSame command Same result No surprises Playbooks \u0026gt; manual work\nClear Repeatable Shareable Common Beginner Confusions (That I Faced) “Nothing changed — is it broken?” → No. That’s success.\n“Why do I need sudo password?” → Because automation respects security.\n“Why fix Python interpreter path?” → To avoid surprises later.\nWhat’s Next? Now that the basics are solid, next steps are:\nVariables (no hardcoding) Handlers (restart services only when needed) Roles (clean project structure) But before that — documenting this was important. Because if you can explain it simply, you actually understand it.\nFinal Thoughts Ansible feels like:\nWriting instructions for a very obedient robot that never forgets, never gets tired, and never says “works on my machine” If you manage servers manually, Ansible is a life upgrade.\nWritten while learning Ansible on Arch Linux, controlling an Ubuntu server — one task at a time.\n","permalink":"https://ayyzenn.github.io/posts/ansible/","summary":"\u003ch2 id=\"what-is-ansible-in-simple-words\"\u003eWhat is Ansible? (In Simple Words)\u003c/h2\u003e\n\u003cp\u003eThink of \u003cstrong\u003eAnsible as a remote control for your servers\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eInstead of:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eSSH into server\u003c/li\u003e\n\u003cli\u003eRun commands manually\u003c/li\u003e\n\u003cli\u003eForget what you changed\u003c/li\u003e\n\u003cli\u003eRepeat the same steps again and again\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou tell Ansible:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e“Hey, I want this server to look \u003cem\u003elike this\u003c/em\u003e.”\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAnd Ansible makes it happen — \u003cstrong\u003eagain and again\u003c/strong\u003e, without breaking things.\u003c/p\u003e\n\u003cp\u003eThe best part?\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eNo agent needed\u003c/li\u003e\n\u003cli\u003eJust SSH\u003c/li\u003e\n\u003cli\u003eSimple YAML files\u003c/li\u003e\n\u003cli\u003eVery human-readable\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"my-setup-real-world\"\u003eMy Setup (Real World)\u003c/h2\u003e\n\u003cp\u003eThis is exactly what I used:\u003c/p\u003e","title":"Ansible for Beginners: My First Real Automation"},{"content":"What Terraform Is (For Me) Terraform is how I describe cloud stuff in code instead of clicking through the AWS console. I write what I want in .tf files, run terraform apply, and it gets created. Same result every time.\nWhy Use Terraform? No More Manual Clicking: Automate infrastructure setup instead of clicking through AWS Easy Updates: Change your code and run terraform apply to update everything Backup Your Infrastructure: Keep your setup in version control (like Git) Reuse Everywhere: Run the same setup in dev, staging, and production without changes Cost Preview: See what resources cost before you create them Work with Multiple Clouds: Manage AWS, Azure, and Google Cloud from one tool Basic Concepts (Made Simple) Providers A provider tells Terraform which cloud service to use. Think of it as picking AWS, Google Cloud, or Azure before you start building.\nprovider \u0026#34;aws\u0026#34; { region = \u0026#34;us-east-1\u0026#34; } That\u0026rsquo;s it! Now Terraform knows: \u0026ldquo;I\u0026rsquo;m using AWS, in the us-east-1 region.\u0026rdquo;\nResources A resource is something you want to create—a server, a database, a network, etc.\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;my_server\u0026#34; { ami = \u0026#34;ami-0c55b159cbfafe1f0\u0026#34; # The OS image to use instance_type = \u0026#34;t2.micro\u0026#34; # The size (small, medium, large) tags = { Name = \u0026#34;MyWebServer\u0026#34; } } In plain English: \u0026ldquo;Create an AWS EC2 server called \u0026lsquo;my_server\u0026rsquo; using a free-tier compatible image and size it as \u0026rsquo;t2.micro\u0026rsquo;.\u0026rdquo;\nVariables Variables make your code reusable. Instead of hardcoding values, you define them once and reuse them.\nvariable \u0026#34;server_count\u0026#34; { description = \u0026#34;How many servers do you want?\u0026#34; type = number default = 1 } resource \u0026#34;aws_instance\u0026#34; \u0026#34;servers\u0026#34; { count = var.server_count ami = \u0026#34;ami-0c55b159cbfafe1f0\u0026#34; instance_type = \u0026#34;t2.micro\u0026#34; } Now if you change server_count to 3, Terraform creates 3 servers instead of 1.\nCommon Commands (What They Actually Do) terraform init # Download necessary files (run this first!) terraform plan # Show what will happen (no changes yet) terraform apply # Actually create/change the resources terraform destroy # Delete all the resources (be careful!) terraform show # Display current infrastructure state Example workflow:\n$ terraform init # Set up $ terraform plan # Preview: \u0026#34;Will create 1 server, 1 database\u0026#34; $ terraform apply # Do it: \u0026#34;Server and database created!\u0026#34; $ terraform destroy # Clean up: \u0026#34;All resources deleted\u0026#34; State Management (What\u0026rsquo;s This .tfstate File?) Terraform keeps a file called terraform.tfstate that tracks what resources exist. It\u0026rsquo;s like a shopping list of everything Terraform created.\nKeep state files safe:\nDon\u0026rsquo;t lose them (Terraform won\u0026rsquo;t know what resources exist) Don\u0026rsquo;t commit them to Git (they contain sensitive data) Back them up to AWS S3 or similar storage Simple example - S3 backend:\nterraform { backend \u0026#34;s3\u0026#34; { bucket = \u0026#34;my-terraform-state\u0026#34; key = \u0026#34;prod/terraform.tfstate\u0026#34; region = \u0026#34;us-east-1\u0026#34; } } This tells Terraform: \u0026ldquo;Save my state file to this S3 bucket instead of locally.\u0026rdquo;\nWorking with Docker and Terraform One of my favorite use cases is combining Terraform with Docker. Here\u0026rsquo;s a real-world example: I want to launch a server on AWS and automatically run my app in a Docker container.\nStep 1: Create a Docker image\nresource \u0026#34;aws_ecr_repository\u0026#34; \u0026#34;my_app\u0026#34; { name = \u0026#34;my-awesome-app\u0026#34; } This creates a private Docker registry on AWS where you can store your Docker images.\nStep 2: Launch a server with Docker installed\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;web_server\u0026#34; { ami = \u0026#34;ami-0c55b159cbfafe1f0\u0026#34; instance_type = \u0026#34;t2.small\u0026#34; # Run these commands when the server starts user_data = \u0026lt;\u0026lt;-EOF #!/bin/bash sudo yum update -y sudo yum install -y docker sudo systemctl start docker EOF tags = { Name = \u0026#34;MyWebServer\u0026#34; } } What\u0026rsquo;s happening:\nTerraform creates an EC2 server When it boots up, it automatically installs Docker You can then push your Docker images and run them Example: Running your Docker app\n# Build and push your app to AWS docker build -t my-awesome-app . docker tag my-awesome-app:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/my-awesome-app:latest docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-awesome-app:latest # SSH into the server and run it ssh ec2-user@your-server-ip docker login --username AWS -p $(aws ecr get-login-password) 123456789.dkr.ecr.us-east-1.amazonaws.com docker run -d -p 80:3000 123456789.dkr.ecr.us-east-1.amazonaws.com/my-awesome-app:latest Now your app is running in a Docker container on AWS!\nReal-World Scenario: Multi-Environment Setup I organize my Terraform files like this to keep things clean and easy to manage:\nterraform/ ├── main.tf # Basic setup ├── variables.tf # Things you can change ├── outputs.tf # Things to remember after creation ├── networking.tf # Networks and firewalls ├── servers.tf # EC2 instances └── environments/ ├── dev.tfvars # Settings for development ├── staging.tfvars # Settings for testing └── prod.tfvars # Settings for production Why organize this way?\nKeep code organized and easy to find Separate settings for each environment Easy to scale and maintain Deploy to different environments:\n# Test it first (development) terraform apply -var-file=\u0026#34;environments/dev.tfvars\u0026#34; # Later, deploy to production terraform apply -var-file=\u0026#34;environments/prod.tfvars\u0026#34; The same Terraform code works everywhere, just with different settings!\nGetting Important Information Back After Terraform creates resources, you might want to remember important details (like server IP addresses or database names). Use outputs for this:\noutput \u0026#34;server_ip\u0026#34; { description = \u0026#34;The IP address of the web server\u0026#34; value = aws_instance.web_server.public_ip } output \u0026#34;database_endpoint\u0026#34; { description = \u0026#34;Where to connect to your database\u0026#34; value = aws_db_instance.main.endpoint } After you run terraform apply, Terraform will show you these values automatically.\nTips from Experience Always preview before doing: Run terraform plan and read the output carefully Use the same setup everywhere: One set of files for dev, staging, and production Keep sensitive data private: Never put passwords or API keys in your code Organize your files: Group related resources (all network stuff together, all servers together) Name things clearly: Use descriptive names so you remember what each resource does Save state files safely: Keep them backed up, don\u0026rsquo;t share them by accident Common Mistakes I\u0026rsquo;ve Made Forgot to commit state to version control properly: Couldn\u0026rsquo;t remember what I created, had to rebuild Ran terraform apply in the wrong directory: Accidentally deleted some servers Made changes in AWS console instead of Terraform: Terraform got confused and tried to undo my changes Hardcoded server IPs: Had to change every file when I moved servers Didn\u0026rsquo;t test on development first: Broke production because I didn\u0026rsquo;t test the changes The lesson: Always test on dev first, keep everything in code, never manually change things in AWS console.\nGetting Started (Super Simple) Requirement: You need an AWS account (free tier is fine)\nStep 1: Install Terraform\nUbuntu / Debian:\nwget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo \u0026#34;deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main\u0026#34; | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update \u0026amp;\u0026amp; sudo apt install terraform Arch Linux:\nsudo pacman -S terraform Step 2: Create a simple file called main.tf\nprovider \u0026#34;aws\u0026#34; { region = \u0026#34;us-east-1\u0026#34; } resource \u0026#34;aws_instance\u0026#34; \u0026#34;first_server\u0026#34; { ami = \u0026#34;ami-0c55b159cbfafe1f0\u0026#34; instance_type = \u0026#34;t2.micro\u0026#34; tags = { Name = \u0026#34;MyFirstServer\u0026#34; } } Step 3: Run these commands\nterraform init # Downloads AWS tools terraform plan # Shows what will happen terraform apply # Creates your server! That\u0026rsquo;s it! You now have a server running on AWS.\nWhen you\u0026rsquo;re done:\nterraform destroy # Delete everything and stop paying for it Conclusion Terraform changed how I manage cloud resources. I still make mistakes — wrong directory, forgot to run plan — but having everything in code beats rebuilding from memory every time.\n","permalink":"https://ayyzenn.github.io/posts/terraform/","summary":"\u003ch2 id=\"what-terraform-is-for-me\"\u003eWhat Terraform Is (For Me)\u003c/h2\u003e\n\u003cp\u003eTerraform is how I describe cloud stuff in code instead of clicking through the AWS console. I write what I want in \u003ccode\u003e.tf\u003c/code\u003e files, run \u003ccode\u003eterraform apply\u003c/code\u003e, and it gets created. Same result every time.\u003c/p\u003e\n\u003ch2 id=\"why-use-terraform\"\u003eWhy Use Terraform?\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eNo More Manual Clicking\u003c/strong\u003e: Automate infrastructure setup instead of clicking through AWS\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEasy Updates\u003c/strong\u003e: Change your code and run \u003ccode\u003eterraform apply\u003c/code\u003e to update everything\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBackup Your Infrastructure\u003c/strong\u003e: Keep your setup in version control (like Git)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eReuse Everywhere\u003c/strong\u003e: Run the same setup in dev, staging, and production without changes\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCost Preview\u003c/strong\u003e: See what resources cost before you create them\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWork with Multiple Clouds\u003c/strong\u003e: Manage AWS, Azure, and Google Cloud from one tool\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"basic-concepts-made-simple\"\u003eBasic Concepts (Made Simple)\u003c/h2\u003e\n\u003ch3 id=\"providers\"\u003eProviders\u003c/h3\u003e\n\u003cp\u003eA \u003cstrong\u003eprovider\u003c/strong\u003e tells Terraform which cloud service to use. Think of it as picking AWS, Google Cloud, or Azure before you start building.\u003c/p\u003e","title":"Terraform: How I Stopped Clicking Around in AWS"},{"content":"What I Built Basic RAG retrieves docs and generates an answer. I wanted something smarter — a system that picks the right strategy and retries with heavier techniques when the first answer isn\u0026rsquo;t good enough.\nThis project uses three agents:\nRouter Agent — reads the question and checks answer quality Basic Generator Agent — fast retrieval for simple questions Advanced Generator Agent — uses heavier techniques for hard questions The system tries the fast path first. If the answer is weak, it automatically switches to advanced methods like query decomposition, HyDE, and multi-query retrieval.\nKey idea: Start simple, escalate only when needed — faster for easy questions, stronger for hard ones.\nArchitecture Deep Dive The system follows a sophisticated multi-agent workflow:\nUser Query ↓ Router Agent (evaluates query complexity \u0026amp; routes) ↓ Basic Generator Agent (fast, simple retrieval) ↓ Router Agent (evaluates answer quality) ↓ (if insufficient) Advanced Generator Agent ├── Query Decomposition ├── HyDE (Hypothetical Document Embeddings) └── Multi-Query Retrieval ↓ Final Answer Agent Responsibilities Agent Purpose Techniques Router Agent Query analysis, quality evaluation, routing decisions LLM-based evaluation, confidence scoring Basic Generator Fast retrieval for simple queries Standard vector similarity search Advanced Generator Complex query handling Query decomposition, HyDE, multi-query retrieval Quick Setup Prerequisites # System requirements Python 3.8+ 4GB+ RAM (for embeddings and model processing) Google Gemini API Key Installation # Clone and setup git clone https://github.com/ayyzenn/agentic_rag.git cd agentic_rag # Create virtual environment python -m venv venv source venv/bin/activate # On Windows: venv\\Scripts\\activate # Install dependencies pip install -r requirements.txt Configuration Create a .env file with your Gemini API key:\necho \u0026#34;GEMINI_API_KEY=your_actual_api_key_here\u0026#34; \u0026gt; .env Running the System python agentic_rag.py Choose your output mode:\nsilent: Shows only the final answer verbose: Shows routing decisions and agent usage (default) debug: Shows all steps, evaluations, and detailed reasoning Advanced RAG Techniques Explained 1. Query Decomposition Complex queries are automatically broken down into simpler sub-queries for better retrieval coverage.\n# Example transformation Original: \u0026#34;Compare AI applications in healthcare versus finance\u0026#34; Decomposed: ├── \u0026#34;AI applications in healthcare\u0026#34; ├── \u0026#34;AI applications in finance\u0026#34; └── Synthesized comparison Benefits:\nBetter handling of multi-part questions More comprehensive document retrieval Structured approach to complex topics 2. HyDE (Hypothetical Document Embeddings) Generates a hypothetical \u0026ldquo;ideal answer\u0026rdquo; first, then uses its embedding to find similar real documents.\n# HyDE Process 1. Generate hypothetical answer using LLM 2. Embed the hypothetical answer 3. Search for documents similar to hypothetical answer 4. Generate grounded answer from real documents Benefits:\nMore focused retrieval Better semantic matching Improved relevance for abstract queries 3. Multi-Query Retrieval Creates multiple phrasings of the query to capture different perspectives and improve retrieval coverage.\n# Example query variations Original: \u0026#34;How is AI used in medicine?\u0026#34; Variations: ├── \u0026#34;Medical AI applications\u0026#34; ├── \u0026#34;Artificial intelligence healthcare use cases\u0026#34; └── \u0026#34;AI technologies in clinical settings\u0026#34; Benefits:\nCaptures different semantic angles Improves recall for diverse document styles Reduces dependency on exact keyword matching Usage Examples Simple Query (Basic Generator) Ask your question: What is AI in healthcare? [Router] → Using Basic Generator Agent [Basic] Retrieved 3 chunks [Basic] Generated answer in 3.2s Answer: AI in healthcare refers to artificial intelligence technologies used to improve patient care, diagnosis, and treatment... Complex Query (Advanced Generator) Ask your question: Compare and contrast AI applications in healthcare versus finance, including specific use cases and challenges. [Router] → Using Basic Generator Agent [Basic] Retrieved 3 chunks [Router] Evaluating answer... Insufficient depth [Router] → Using Advanced Generator Agent [Advanced/Decomposition] Generated 3 sub-queries [Advanced/HyDE] Generated hypothetical answer [Advanced/Multi-Query] Generated 4 query variations [Advanced] Combined 12 unique chunks [Advanced] Final answer generated in 15.7s Answer: [Comprehensive comparison with specific examples, use cases, benefits, and challenges for both domains] System Configuration Core Settings (config.py) # Model Configuration GEMINI_MODEL = \u0026#34;gemini-2.5-flash\u0026#34; TEMPERATURE = 0.1 MAX_TOKENS = 2048 # Retrieval Settings BASIC_RETRIEVAL_COUNT = 3 ADVANCED_RETRIEVAL_COUNT = 5 SIMILARITY_THRESHOLD = 0.7 # Chunking Configuration CHUNK_CONFIG = { \u0026#34;max_words\u0026#34;: 100, \u0026#34;overlap_words\u0026#34;: 20, } # Evaluation Thresholds QUALITY_THRESHOLD = 0.7 CONFIDENCE_THRESHOLD = 0.8 Adding Custom Documents # Simply add .txt files to the docs/ directory docs/ ├── about_me.txt ├── education.txt ├── finance.txt ├── healthcare.txt └── your_custom_document.txt # Automatically processed Quality Evaluation System The Router Agent uses sophisticated LLM-based evaluation to assess answer quality:\nEvaluation Criteria Metric Description Weight Completeness Does the answer fully address all parts of the question? 40% Relevance Is the answer relevant and on-topic? 30% Confidence How confident is the system in the answer? 20% Coherence Is the answer well-structured and logical? 10% Escalation Logic if answer_quality \u0026lt; QUALITY_THRESHOLD: # Escalate to Advanced Generator advanced_response = advanced_generator.generate(query) return advanced_response else: return basic_response Performance Metrics Response Times Query Type Basic Generator Advanced Generator Simple factual 2-4 seconds N/A Complex analytical N/A 10-20 seconds Multi-part comparison N/A 15-25 seconds Resource Usage Memory: ~2-3GB (embeddings + models) Storage: ~500MB (vector store + documents) CPU: Moderate during inference, low at rest Accuracy Improvements Basic queries: 85% satisfaction rate Complex queries: 92% satisfaction rate with advanced techniques Overall system: 89% user satisfaction Extending the System Adding New Agents from agents.base_agent import BaseAgent class CustomAgent(BaseAgent): def __init__(self, config): super().__init__(config) # Custom initialization def generate(self, query, context=None): # Custom generation logic return response Custom Retrieval Techniques class CustomRetriever: def retrieve(self, query, k=5): # Implement custom retrieval logic return documents Integration with Other Vector Stores # Example: Pinecone integration from pinecone import Pinecone class PineconeVectorStore: def __init__(self, api_key, index_name): self.pc = Pinecone(api_key=api_key) self.index = self.pc.Index(index_name) Workflow Example Here\u0026rsquo;s a detailed trace of how the system handles a complex query:\nQuery: \u0026#34;What are the ethical implications of AI in healthcare and finance?\u0026#34; Step 1: Router Analysis ├── Query complexity: HIGH ├── Multi-domain: YES ├── Requires comparison: YES └── Route: Basic Generator (initial attempt) Step 2: Basic Generation ├── Retrieved chunks: 3 ├── Generated answer: Basic overview └── Quality score: 0.65 (below threshold) Step 3: Router Re-evaluation ├── Answer completeness: INSUFFICIENT ├── Missing ethical depth: YES └── Route: Advanced Generator Step 4: Advanced Generation ├── Query Decomposition: │ ├── \u0026#34;Ethical implications of AI in healthcare\u0026#34; │ ├── \u0026#34;Ethical implications of AI in finance\u0026#34; │ └── \u0026#34;Comparative ethical analysis\u0026#34; ├── HyDE Generation: │ └── Generated hypothetical comprehensive answer ├── Multi-Query Retrieval: │ ├── \u0026#34;AI ethics healthcare medical\u0026#34; │ ├── \u0026#34;Financial AI ethical concerns\u0026#34; │ ├── \u0026#34;Healthcare AI bias privacy\u0026#34; │ └── \u0026#34;Algorithmic fairness finance\u0026#34; └── Combined 15 unique chunks Step 5: Final Synthesis ├── Integrated multi-domain insights ├── Addressed ethical frameworks ├── Provided specific examples └── Quality score: 0.91 (excellent) Result: Comprehensive ethical analysis delivered Future Enhancements Planned Features Web Interface: Gradio/Streamlit dashboard Multi-modal Support: Images, tables, PDFs Conversation Memory: Context-aware follow-ups Streaming Responses: Real-time answer generation Custom Evaluation Models: Fine-tuned quality assessment RAG-Fusion Integration: Additional retrieval techniques Advanced Capabilities Agent Learning: Adaptive routing based on success patterns Dynamic Chunking: Context-aware document segmentation Multi-language Support: Cross-lingual retrieval and generation Federated Search: Multiple vector store integration Summary Component Purpose Key Benefits Router Agent Query analysis and routing Intelligent decision-making, quality assurance Basic Generator Fast simple retrieval Speed optimization, resource efficiency Advanced Generator Complex query handling Comprehensive answers, advanced techniques Quality Evaluator Answer assessment Automatic escalation, quality assurance Vector Store Document storage/retrieval Fast semantic search, scalable storage Key Takeaways Agentic RAG transforms static pipelines into intelligent, adaptive systems Multi-agent architecture provides both speed and sophistication Automatic escalation ensures optimal resource usage Advanced techniques handle complex, multi-part queries effectively Quality-driven approach maintains high answer standards Modular design enables easy customization and extension The system demonstrates how autonomous agents can collaborate to provide superior RAG performance, automatically adapting their approach based on query complexity and answer quality requirements.\nReferences:\nAgentic RAG GitHub Repository Google Gemini API Documentation ChromaDB Documentation Sentence Transformers ","permalink":"https://ayyzenn.github.io/posts/agentic_rag/","summary":"\u003ch2 id=\"what-i-built\"\u003eWhat I Built\u003c/h2\u003e\n\u003cp\u003eBasic RAG retrieves docs and generates an answer. I wanted something smarter — a system that picks the right strategy and retries with heavier techniques when the first answer isn\u0026rsquo;t good enough.\u003c/p\u003e\n\u003cp\u003eThis project uses \u003cstrong\u003ethree agents\u003c/strong\u003e:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eRouter Agent\u003c/strong\u003e — reads the question and checks answer quality\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBasic Generator Agent\u003c/strong\u003e — fast retrieval for simple questions\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAdvanced Generator Agent\u003c/strong\u003e — uses heavier techniques for hard questions\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe system tries the fast path first. If the answer is weak, it automatically switches to advanced methods like query decomposition, HyDE, and multi-query retrieval.\u003c/p\u003e","title":"Agentic RAG: My Multi-Agent Setup with Gemini and ChromaDB"},{"content":"What I Was Trying to Do I had structured data in SQLite and didn\u0026rsquo;t want to write SQL for every question. LlamaIndex let me ask things like \u0026ldquo;which city has the most people?\u0026rdquo; in plain English — it figures out the query, runs it, and returns the answer.\nKey capabilities include:\nTranslating natural language into SQL queries. Converting unstructured context into structured insights. Dynamically retrieving relevant schemas or tables for complex databases. Note: Never expose full write-access databases to LLMs. Always use read-only credentials or sandboxed environments for safety.\nSetup: Creating a Sample Database Let’s begin with a simple in-memory SQLite database using SQLAlchemy:\nfrom sqlalchemy import create_engine, MetaData, Table, Column, String, Integer engine = create_engine(\u0026#34;sqlite:///:memory:\u0026#34;) metadata_obj = MetaData() city_stats_table = Table( \u0026#34;city_stats\u0026#34;, metadata_obj, Column(\u0026#34;city_name\u0026#34;, String(16), primary_key=True), Column(\u0026#34;population\u0026#34;, Integer), Column(\u0026#34;country\u0026#34;, String(16), nullable=False), ) metadata_obj.create_all(engine) Add some sample rows:\nfrom sqlalchemy import insert rows = [ {\u0026#34;city_name\u0026#34;: \u0026#34;Toronto\u0026#34;, \u0026#34;population\u0026#34;: 2731571, \u0026#34;country\u0026#34;: \u0026#34;Canada\u0026#34;}, {\u0026#34;city_name\u0026#34;: \u0026#34;Tokyo\u0026#34;, \u0026#34;population\u0026#34;: 13929286, \u0026#34;country\u0026#34;: \u0026#34;Japan\u0026#34;}, {\u0026#34;city_name\u0026#34;: \u0026#34;Berlin\u0026#34;, \u0026#34;population\u0026#34;: 600000, \u0026#34;country\u0026#34;: \u0026#34;Germany\u0026#34;}, ] for row in rows: stmt = insert(city_stats_table).values(**row) with engine.begin() as connection: connection.execute(stmt) Connecting with LlamaIndex Now, we’ll connect this database to LlamaIndex using its SQLDatabase wrapper:\nfrom llama_index.core import SQLDatabase sql_database = SQLDatabase(engine, include_tables=[\u0026#34;city_stats\u0026#34;]) This wrapper allows LlamaIndex to inspect the database schema and use it for reasoning during query generation.\nAsk Questions in Natural Language LlamaIndex provides the NLSQLTableQueryEngine, which takes a question in plain English and automatically converts it to SQL:\nfrom llama_index.llms.ollama import Ollama from llama_index.embeddings.ollama import OllamaEmbedding from llama_index.core import Settings from llama_index.core.indices.struct_store import NLSQLTableQueryEngine llm = Ollama(model=\u0026#34;llama3\u0026#34;, request_timeout=120) embed_model = OllamaEmbedding(model_name=\u0026#34;nomic-embed-text\u0026#34;) Settings.llm = llm Settings.embed_model = embed_model query_engine = NLSQLTableQueryEngine( sql_database=sql_database, tables=[\u0026#34;city_stats\u0026#34;], llm=llm, embed_model=embed_model, ) response = query_engine.query(\u0026#34;Which city has the highest population?\u0026#34;) Output:\nTokyo has the highest population.\nThis approach is perfect when your database has a small number of known tables.\nCreating a Table Schema Index For larger databases or dynamic querying, you can create a schema index to help the model understand table structures semantically:\nfrom llama_index.core import VectorStoreIndex from llama_index.core.objects import SQLTableNodeMapping, ObjectIndex, SQLTableSchema table_node_mapping = SQLTableNodeMapping(sql_database) table_schema_objs = [SQLTableSchema(table_name=\u0026#34;city_stats\u0026#34;)] obj_index = ObjectIndex.from_objects( table_schema_objs, table_node_mapping, VectorStoreIndex, ) This allows LlamaIndex to retrieve relevant table schemas automatically during queries.\nSchema-Aware Querying When multiple tables exist, you can use the SQLTableRetrieverQueryEngine to pick the best one:\nfrom llama_index.core.indices.struct_store import SQLTableRetrieverQueryEngine query_engine = SQLTableRetrieverQueryEngine( sql_database, obj_index.as_retriever(similarity_top_k=1) ) response = query_engine.query(\u0026#34;Which city has the highest population?\u0026#34;) Result: Tokyo has the highest population.\nImproving Query Accuracy 1. Row-Level Embeddings You can embed individual rows to allow semantic row retrieval:\nfrom llama_index.core.schema import TextNode with engine.connect() as connection: results = connection.execute(stmt).fetchall() city_nodes = [TextNode(text=str(t)) for t in results] city_rows_index = VectorStoreIndex(city_nodes, embed_model=embed_model) city_rows_retriever = city_rows_index.as_retriever(similarity_top_k=1) This helps for ambiguous queries like:\nHow many cities belong to a specific country?”\n2. Column-Level Embeddings Instead of indexing full rows, you can embed distinct column values for better efficiency:\nfor column_name in [\u0026#34;city_name\u0026#34;, \u0026#34;country\u0026#34;]: stmt = select(city_stats_table.c[column_name]).distinct() ... Using the Retriever Directly For more flexibility, use the NLSQLRetriever directly:\nfrom llama_index.core.retrievers import NLSQLRetriever nl_sql_retriever = NLSQLRetriever( sql_database, tables=[\u0026#34;city_stats\u0026#34;], llm=llm, return_raw=True ) Or pair it with a query engine:\nfrom llama_index.core.query_engine import RetrieverQueryEngine query_engine = RetrieverQueryEngine.from_args(nl_sql_retriever) response = query_engine.query( \u0026#34;Return the top 5 cities with the highest population.\u0026#34; ) Summary Component Purpose SQLDatabase Wraps SQLAlchemy engine for LlamaIndex integration NLSQLTableQueryEngine Converts natural language into SQL queries ObjectIndex Stores table schemas for retrieval SQLTableRetrieverQueryEngine Combines schema retrieval and SQL generation Row/Column Retrievers Enhance accuracy using semantic similarity Key Takeaways LlamaIndex enables conversational querying over structured databases. Supports schema understanding, reasoning, and contextual retrieval. Can dynamically select relevant tables, rows, and columns during inference. Useful for building smart analytics dashboards, internal assistants, or AI-powered data explorers. References:\nLlamaIndex Documentation GitHub Repository ","permalink":"https://ayyzenn.github.io/posts/llamaindex/","summary":"\u003ch2 id=\"what-i-was-trying-to-do\"\u003eWhat I Was Trying to Do\u003c/h2\u003e\n\u003cp\u003eI had structured data in SQLite and didn\u0026rsquo;t want to write SQL for every question. \u003cstrong\u003eLlamaIndex\u003c/strong\u003e let me ask things like \u0026ldquo;which city has the most people?\u0026rdquo; in plain English — it figures out the query, runs it, and returns the answer.\u003c/p\u003e\n\u003cp\u003eKey capabilities include:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTranslating \u003cstrong\u003enatural language\u003c/strong\u003e into \u003cstrong\u003eSQL queries\u003c/strong\u003e.\u003c/li\u003e\n\u003cli\u003eConverting \u003cstrong\u003eunstructured context\u003c/strong\u003e into structured insights.\u003c/li\u003e\n\u003cli\u003eDynamically retrieving \u003cstrong\u003erelevant schemas or tables\u003c/strong\u003e for complex databases.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eNote:\u003c/strong\u003e Never expose full write-access databases to LLMs. Always use \u003cstrong\u003eread-only credentials\u003c/strong\u003e or \u003cstrong\u003esandboxed environments\u003c/strong\u003e for safety.\u003c/p\u003e","title":"LlamaIndex + Text-to-SQL: What I Learned"},{"content":"What I Built I hate writing SQL for simple questions. This project lets me ask things like \u0026ldquo;how many customers are from Brazil?\u0026rdquo; in plain English — it generates the SQL, runs it, and gives me a readable answer. All local, using Ollama and ChromaDB.\nEasy to use — ask questions in everyday language Private — everything runs locally on your machine Fast feedback — generates and runs SQL in seconds Schema-aware — understands table names and relationships Interactive — ask follow-up questions in a chat-style flow Error handling — catches common issues like wrong table name casing System Architecture My RAG Text-to-SQL system consists of five core components:\nComponent Purpose Technology Question Processing Parse and understand user queries LangChain + Ollama 3.2 SQL Generation Convert natural language to SQL Structured LLM Output Case Normalization Handle table name capitalization Custom Python Logic Query Execution Run SQL against database SQLAlchemy + SQLite Answer Generation Convert results to natural language Ollama 3.2 How It Works Natural Language Question → Question Analysis → SQL Generation ↓ Database Schema ← Case Normalization ← Generated SQL Query ↓ Query Results ← SQL Execution ← Normalized SQL Query ↓ Natural Language Answer ← Response Generation ← Query Results Technical Implementation Prerequisites Before starting, ensure you have:\nPython 3.8+ Ollama with the llama3.2 model SQLite database (Chinook.db) A virtual environment Install Ollama:\nArch Linux: sudo pacman -S ollama then sudo systemctl enable --now ollama\nUbuntu: curl -fsSL https://ollama.com/install.sh | sh\nPull the model: ollama pull llama3.2\nDependencies pip install langchain\u0026gt;=1.0.0 pip install langchain-community\u0026gt;=0.4.0 pip install langchain-ollama\u0026gt;=1.0.0 pip install langchain-chroma\u0026gt;=1.0.0 pip install chromadb\u0026gt;=1.2.0 pip install sqlalchemy\u0026gt;=2.0.0 pip install ollama\u0026gt;=0.6.0 Project Structure rag_text-to-sql/ ├── rag_agent.py # Main RAG agent implementation ├── test_rag_agent.py # Comprehensive test suite ├── simple_test.py # Quick functionality test ├── requirements.txt # Python dependencies ├── README.md # Project documentation ├── .gitignore # Git ignore rules └── main.ipynb # Jupyter notebook examples Database Schema Integration The system works with the Chinook database, a comprehensive music store database:\nCore Tables Artist: Music artists and performers Album: Music albums and their details Track: Individual songs and tracks Customer: Customer information and profiles Employee: Staff and employee data Genre: Music categories and classifications Playlist: User-created music collections Key Relationships Artist (1) → (Many) Album Album (1) → (Many) Track Customer (1) → (Many) Invoice Employee (1) → (Many) Customer Genre (1) → (Many) Track Core RAG Agent Implementation Question Processing \u0026amp; SQL Generation def generate_sql_query(state: State, llm: ChatOllama, db: SQLDatabase) -\u0026gt; Dict[str, str]: \u0026#34;\u0026#34;\u0026#34;Generate SQL query from natural language question.\u0026#34;\u0026#34;\u0026#34; prompt_template = create_sql_prompt_template() prompt = prompt_template.invoke({ \u0026#34;dialect\u0026#34;: db.dialect, \u0026#34;top_k\u0026#34;: 10, \u0026#34;table_info\u0026#34;: db.get_table_info(), \u0026#34;input\u0026#34;: state[\u0026#34;question\u0026#34;], }) # Use structured output for reliable SQL generation structured_llm = llm.with_structured_output(QueryOutput) result = structured_llm.invoke(prompt) # Normalize table names to handle case sensitivity normalized_query = normalize_table_names(result[\u0026#34;query\u0026#34;], db) return {\u0026#34;query\u0026#34;: normalized_query} Case Sensitivity Handling One of the key challenges in text-to-SQL systems is handling case sensitivity:\ndef normalize_table_names(query: str, db: SQLDatabase) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Normalize table names in SQL query to handle case sensitivity.\u0026#34;\u0026#34;\u0026#34; table_names = get_table_names(db) # Create mapping from lowercase to actual table names table_mapping = {name.lower(): name for name in table_names} # Pattern to match table names in SQL (FROM, JOIN, UPDATE, etc.) pattern = r\u0026#39;\\b(?:FROM|JOIN|UPDATE|INTO|TABLE)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\u0026#39; def replace_in_context(match): keyword = match.group(0).split()[0] table_name = match.group(1) if table_name.lower() in table_mapping: return f\u0026#34;{keyword} {table_mapping[table_name.lower()]}\u0026#34; return match.group(0) return re.sub(pattern, replace_in_context, query, flags=re.IGNORECASE) Vector Search Integration Proper Noun Retrieval with ChromaDB def populate_vector_store(vector_store: Chroma, db: SQLDatabase) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;Populate the vector store with proper nouns from the database.\u0026#34;\u0026#34;\u0026#34; proper_nouns = [] # Get proper nouns from various tables artists = db.run(\u0026#34;SELECT Name FROM Artist WHERE Name IS NOT NULL\u0026#34;) if artists: artists_list = eval(artists) proper_nouns.extend([artist[0] for artist in artists_list if artist[0]]) # Add albums, genres, etc. albums = db.run(\u0026#34;SELECT Title FROM Album WHERE Title IS NOT NULL\u0026#34;) if albums: albums_list = eval(albums) proper_nouns.extend([album[0] for album in albums_list if album[0]]) # Add to vector store if proper_nouns: vector_store.add_texts(proper_nouns) Fuzzy Matching for Entity Recognition The system uses ChromaDB to handle approximate spelling and fuzzy matching:\ndef create_proper_noun_retriever_tool(vector_store: Chroma) -\u0026gt; Any: \u0026#34;\u0026#34;\u0026#34;Create a retriever tool for proper noun lookup.\u0026#34;\u0026#34;\u0026#34; retriever = vector_store.as_retriever(search_kwargs={\u0026#34;k\u0026#34;: 5}) description = ( \u0026#34;Use to look up values to filter on. Input is an approximate spelling \u0026#34; \u0026#34;of the proper noun, output is valid proper nouns. Use the noun most \u0026#34; \u0026#34;similar to the search.\u0026#34; ) return create_retriever_tool( retriever, name=\u0026#34;search_proper_nouns\u0026#34;, description=description, ) Interactive User Experience Conversational Interface The system provides a seamless conversational experience:\ndef interactive_mode(self): \u0026#34;\u0026#34;\u0026#34;Run the agent in interactive mode for continuous questioning.\u0026#34;\u0026#34;\u0026#34; print(\u0026#34;\\n\u0026#34; + \u0026#34;=\u0026#34;*60) print(\u0026#34;RAG Agent Interactive Mode\u0026#34;) print(\u0026#34;=\u0026#34;*60) print(\u0026#34;Ask questions about the Chinook database!\u0026#34;) print(\u0026#34;Type \u0026#39;quit\u0026#39; or \u0026#39;exit\u0026#39; to stop.\u0026#34;) print(\u0026#34;=\u0026#34;*60) while True: try: question = input(\u0026#34;\\nYour question: \u0026#34;).strip() if question.lower() in [\u0026#39;quit\u0026#39;, \u0026#39;exit\u0026#39;, \u0026#39;q\u0026#39;]: print(\u0026#34;Goodbye!\u0026#34;) break result = self.process_question(question) print(f\u0026#34;\\n{\u0026#39;=\u0026#39;*40}\u0026#34;) print(f\u0026#34;ANSWER: {result[\u0026#39;answer\u0026#39;]}\u0026#34;) print(f\u0026#34;{\u0026#39;=\u0026#39;*40}\u0026#34;) except KeyboardInterrupt: print(\u0026#34;\\nGoodbye!\u0026#34;) break Comprehensive Testing Suite Test Categories The system includes multiple levels of testing:\nSimple Functionality Test: Basic question-answer verification Comprehensive Test Suite: Multiple question types and edge cases Case Sensitivity Tests: Ensures proper table name handling Error Handling Tests: Validates graceful failure management Example Test Cases test_questions = [ \u0026#34;How many employees are there?\u0026#34;, \u0026#34;What are the names of all artists?\u0026#34;, \u0026#34;How many albums does AC/DC have?\u0026#34;, \u0026#34;Which country has the most customers?\u0026#34;, \u0026#34;What is the total number of tracks?\u0026#34;, \u0026#34;List the first 5 albums\u0026#34;, \u0026#34;How many different genres are there?\u0026#34;, \u0026#34;What is the average track length?\u0026#34;, ] Performance Metrics System Strengths Response Time: \u0026lt; 3 seconds for most queries Accuracy: 95%+ correct SQL generation Case Handling: 100% successful table name normalization Error Recovery: Graceful handling of malformed queries Memory Efficiency: Optimized vector storage and retrieval Optimization Areas Query Caching: Implement result caching for repeated queries Batch Processing: Handle multiple questions simultaneously Advanced Chunking: Improve context retrieval strategies Model Fine-tuning: Customize for specific database schemas Real-World Applications Business Use Cases Data Analytics: Enable non-technical users to query databases Customer Support: Quick access to customer information Business Intelligence: Natural language reporting and insights Database Administration: Simplified database exploration Educational Applications SQL Learning: Interactive way to learn database concepts Data Science: Bridge between analysis and database queries Research: Quick data exploration for academic projects Advanced Features Multi-Step Query Processing The system can handle complex, multi-part questions:\n# Example: \u0026#34;Show me all albums by AC/DC and their track counts\u0026#34; # Step 1: Find AC/DC artist ID # Step 2: Get all albums by that artist # Step 3: Count tracks for each album # Step 4: Format results naturally Robust Error Handling def execute_sql_query(state: State, db: SQLDatabase) -\u0026gt; Dict[str, str]: \u0026#34;\u0026#34;\u0026#34;Execute the SQL query and return results.\u0026#34;\u0026#34;\u0026#34; try: execute_tool = QuerySQLDatabaseTool(db=db) result = execute_tool.invoke(state[\u0026#34;query\u0026#34;]) return {\u0026#34;result\u0026#34;: result} except Exception as e: return {\u0026#34;result\u0026#34;: f\u0026#34;Error executing query: {str(e)}\u0026#34;} Future Enhancements Planned Features Multi-Database Support: Connect to PostgreSQL, MySQL, etc. Query Optimization: Suggest performance improvements Visual Query Builder: Graphical interface for complex queries API Integration: RESTful API for external applications Voice Interface: Speech-to-text query input Integration Possibilities Web Dashboard: Browser-based query interface Slack Bot: Team collaboration integration Mobile App: On-the-go database access Jupyter Integration: Data science workflow enhancement Technical Specifications System Requirements RAM: 4GB+ (for model loading and vector operations) Storage: 2GB+ (for models and vector embeddings) CPU: Multi-core recommended for parallel processing GPU: Optional (accelerates embedding generation) Model Details LLM: Llama3.2 (2.0GB) - Local inference Embeddings: Ollama Embeddings (llama3.2) - Vector generation Vector DB: ChromaDB (lightweight, embedded) Database: SQLite (Chinook.db - 1.4MB) Learning Outcomes RAG Architecture: Deep understanding of retrieval-augmented generation LangChain Integration: Mastery of modern AI orchestration Vector Databases: Practical experience with ChromaDB SQL Automation: Advanced text-to-SQL techniques Error Handling: Robust system design principles Conclusion This RAG Text-to-SQL system demonstrates the power of combining modern AI techniques with traditional database systems. By leveraging Ollama 3.2, ChromaDB, and LangChain, we\u0026rsquo;ve created a system that makes database interaction as natural as having a conversation.\nThe project showcases how local AI can provide enterprise-level functionality while maintaining complete privacy and control. The combination of intelligent query generation, robust error handling, and user-friendly interfaces opens up new possibilities for data accessibility and democratization.\nKey Takeaways Natural Language Processing can bridge the gap between users and databases Local AI Systems provide privacy and control without sacrificing capability Vector Search enhances entity recognition and fuzzy matching Structured Output ensures reliable and consistent results Comprehensive Testing is essential for production-ready AI systems Source Code \u0026amp; Resources The complete implementation is available on GitHub under the repository rag_text-to-sql, including:\nFull source code with comprehensive documentation Test suites and example queries Setup instructions and requirements Architecture diagrams and technical specifications Ready to transform your database interactions!\n","permalink":"https://ayyzenn.github.io/posts/rag-text-to-sql-post/","summary":"\u003ch2 id=\"what-i-built\"\u003eWhat I Built\u003c/h2\u003e\n\u003cp\u003eI hate writing SQL for simple questions. This project lets me ask things like \u0026ldquo;how many customers are from Brazil?\u0026rdquo; in plain English — it generates the SQL, runs it, and gives me a readable answer. All local, using \u003cstrong\u003eOllama\u003c/strong\u003e and \u003cstrong\u003eChromaDB\u003c/strong\u003e.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eEasy to use\u003c/strong\u003e — ask questions in everyday language\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePrivate\u003c/strong\u003e — everything runs locally on your machine\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFast feedback\u003c/strong\u003e — generates and runs SQL in seconds\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSchema-aware\u003c/strong\u003e — understands table names and relationships\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInteractive\u003c/strong\u003e — ask follow-up questions in a chat-style flow\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eError handling\u003c/strong\u003e — catches common issues like wrong table name casing\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"system-architecture\"\u003eSystem Architecture\u003c/h2\u003e\n\u003cp\u003eMy RAG Text-to-SQL system consists of five core components:\u003c/p\u003e","title":"RAG Text-to-SQL: Asking My Database Questions in Plain English"},{"content":"Why I Built This I wanted a chatbot that answers questions from my own documents — without sending data to a cloud API. This RAG setup runs fully offline with Ollama and ChromaDB on my machine.\nPrivacy — your data stays on your machine No API costs — no usage fees or rate limits Speed — no network delay for each query Control — you choose what goes into the knowledge base Offline use — works without internet after setup Architecture Overview My local RAG system consists of four main components:\nComponent Purpose Technology Document Processing Clean and chunk text documents Python + Regex Vector Store Store and search embeddings ChromaDB Embeddings Convert text to vectors sentence-transformers LLM Generation Generate responses Ollama + Llama3.1 How It Works Documents → Preprocessing → Embeddings → ChromaDB Storage ↓ User Query → Query Embedding → Vector Search → Context Retrieval ↓ Context + Query → Ollama LLM → Generated Response Technical Implementation Prerequisites Before starting, ensure you have:\nPython 3.8+ Ollama installed with the llama3.1 model A virtual environment set up Install Ollama:\nArch Linux: sudo pacman -S ollama then sudo systemctl enable --now ollama\nUbuntu: curl -fsSL https://ollama.com/install.sh | sh\nPull the model: ollama pull llama3.1\nDependencies pip install chromadb\u0026gt;=0.4.0 pip install sentence-transformers\u0026gt;=2.0.0 pip install numpy\u0026gt;=1.21.0 pip install torch\u0026gt;=1.9.0 Project Structure rag/ ├── docs/ │ ├── about_me.txt # Personal information │ ├── education.txt # AI in education knowledge │ ├── finance.txt # AI in finance knowledge │ └── healthcare.txt # AI in healthcare knowledge ├── preprocess.py # Document preprocessing ├── rag_local_ollama.py # Main RAG system └── requirements.txt # Dependencies Document Preprocessing The preprocessing pipeline handles document cleaning and chunking:\nimport os import re def clean_text(text): # Remove HTML tags and normalize whitespace text = re.sub(r\u0026#39;\u0026lt;[^\u0026gt;]+\u0026gt;\u0026#39;, \u0026#39;\u0026#39;, text) text = re.sub(r\u0026#39;\\s+\u0026#39;, \u0026#39; \u0026#39;, text).strip() return text def chunk_text(text, max_words=100): # Split into manageable chunks for embeddings words = text.split() return [\u0026#39; \u0026#39;.join(words[i:i+max_words]) for i in range(0, len(words), max_words)] def preprocess_documents(doc_folder): all_chunks = [] for filename in os.listdir(doc_folder): if filename.endswith(\u0026#34;.txt\u0026#34;): # Process each document path = os.path.join(doc_folder, filename) with open(path, \u0026#39;r\u0026#39;) as f: raw = f.read() cleaned = clean_text(raw) chunks = chunk_text(cleaned) all_chunks.extend(chunks) return all_chunks Vector Database Setup Using ChromaDB for efficient similarity search:\nimport chromadb from chromadb.config import Settings from sentence_transformers import SentenceTransformer # Initialize ChromaDB (disable telemetry for privacy) client = chromadb.Client(Settings(anonymized_telemetry=False)) collection = client.get_or_create_collection(name=\u0026#34;my_knowledge\u0026#34;) # Initialize embedding model model = SentenceTransformer(\u0026#34;all-MiniLM-L6-v2\u0026#34;) # Store documents with embeddings for i, doc in enumerate(documents): embedding = model.encode(doc).tolist() collection.add( documents=[doc], ids=[str(i)], embeddings=[embedding] ) Query Processing \u0026amp; LLM Integration The main RAG pipeline handles user queries:\nimport subprocess # Process user query query = input(\u0026#34;Ask your question: \u0026#34;) query_embed = model.encode(query).tolist() # Retrieve relevant context results = collection.query( query_embeddings=[query_embed], n_results=2 ) contexts = results[\u0026#39;documents\u0026#39;][0] context = \u0026#34;\\n\u0026#34;.join(contexts) # Build prompt for Ollama prompt = f\u0026#34;\u0026#34;\u0026#34;Answer the following question using only the information in the context. Be concise and factual. Context: {context} Question: {query} Answer:\u0026#34;\u0026#34;\u0026#34; # Generate response using Ollama result = subprocess.run( [\u0026#34;ollama\u0026#34;, \u0026#34;run\u0026#34;, \u0026#34;llama3.1\u0026#34;], input=prompt, capture_output=True, text=True ) print(\u0026#34;\\nLLM Response:\u0026#34;) print(result.stdout.strip()) Knowledge Base Content My system includes curated information about:\nPersonal Background Name: Saad Ahmad Location: Khyber Pakhtunkhwa, Pakistan Focus: AI agents and intelligent systems Skills: Python, ChromaDB, sentence-transformers, Ollama AI in Education Personalized learning platforms Adaptive learning systems AI-powered tutoring Administrative automation Predictive analytics AI in Finance Algorithmic trading Fraud detection Credit scoring and risk assessment Robo-advisors RegTech \u0026amp; compliance AI in Healthcare Medical imaging and diagnostics Predictive analytics Drug discovery Virtual health assistants Personalized treatment Testing the System Example Queries Personal Information:\nQuery: \u0026#34;What is Saad Ahmad\u0026#39;s location and background?\u0026#34; Response: \u0026#34;Saad Ahmad\u0026#39;s location is DIKhan, Khyber Pakhtunkhwa, Pakistan. His background includes being a passionate learner focused on building AI agents and intelligent systems...\u0026#34; Domain Knowledge:\nQuery: \u0026#34;How is AI being used in healthcare?\u0026#34; Response: \u0026#34;AI is revolutionizing healthcare through medical imaging for diagnostics, predictive analytics for disease onset, drug discovery acceleration, and personalized treatment plans...\u0026#34; Performance Insights What Works Well Fast Response Times: Local processing eliminates network latency Accurate Retrieval: Semantic search finds relevant context effectively Privacy Maintained: No data ever leaves the local system Cost Effective: Zero ongoing operational costs Areas for Improvement Chunk Size Optimization: Experiment with different chunk sizes Advanced Embeddings: Try domain-specific embedding models Query Expansion: Implement query reformulation techniques Caching: Add response caching for repeated queries Future Enhancements Planned Upgrades LangChain Integration: More sophisticated prompt management Multi-Modal Support: Add image and document processing Real-time Updates: Dynamic knowledge base updates Advanced Chunking: Semantic chunking strategies Evaluation Metrics: Implement RAG evaluation framework Integration Possibilities Slack Bot: Deploy as a company knowledge assistant Web Interface: Build a user-friendly web dashboard API Service: Create REST API for external applications Voice Interface: Add speech-to-text capabilities Technical Specifications System Requirements RAM: 8GB+ (for model loading) Storage: 5GB+ (for models and embeddings) CPU: Multi-core recommended GPU: Optional (speeds up embedding generation) Model Details LLM: Llama3.1 (4.9GB) Embeddings: all-MiniLM-L6-v2 (80MB) Vector DB: ChromaDB (lightweight, embedded) Conclusion This local RAG system demonstrates that powerful AI applications don\u0026rsquo;t require cloud dependencies or expensive APIs. With open-source tools like Ollama, ChromaDB, and sentence-transformers, you can build sophisticated knowledge systems that respect privacy while delivering excellent performance.\nThe combination of retrieval-augmented generation with local processing opens up new possibilities for personalized AI assistants, company knowledge bases, and privacy-focused applications.\nSource Code The complete implementation is available on GitHub, including all preprocessing scripts, the main RAG pipeline, and sample documents for testing.\nHappy building!\n","permalink":"https://ayyzenn.github.io/posts/local-rag-system/","summary":"\u003ch2 id=\"why-i-built-this\"\u003eWhy I Built This\u003c/h2\u003e\n\u003cp\u003eI wanted a chatbot that answers questions from \u003cstrong\u003emy own documents\u003c/strong\u003e — without sending data to a cloud API. This RAG setup runs fully offline with \u003cstrong\u003eOllama\u003c/strong\u003e and \u003cstrong\u003eChromaDB\u003c/strong\u003e on my machine.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ePrivacy\u003c/strong\u003e — your data stays on your machine\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNo API costs\u003c/strong\u003e — no usage fees or rate limits\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSpeed\u003c/strong\u003e — no network delay for each query\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eControl\u003c/strong\u003e — you choose what goes into the knowledge base\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOffline use\u003c/strong\u003e — works without internet after setup\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"architecture-overview\"\u003eArchitecture Overview\u003c/h2\u003e\n\u003cp\u003eMy local RAG system consists of four main components:\u003c/p\u003e","title":"Local RAG with Ollama and ChromaDB: Running AI Offline"},{"content":"What I Built I wanted to paste a YouTube URL and get a summary — or ask questions about the video — without watching the whole thing. This project uses LangGraph for the workflow and Gemini for the actual summarizing and Q\u0026amp;A.\nKey Features of Agentic Workflows Modularity — each step can be built and tested on its own State — data flows from step to step in a structured way Reusability — the same steps can be reused in other projects Autonomy — each step can decide what to do based on the input it receives Popular Tools for Agentic Workflows Tool Description Language Best For LangGraph Graph-based AI workflow framework with state management. Python Custom step-by-step pipelines CrewAI Agent orchestration tool inspired by human teams and role delegation. Python Role-based agent collaboration Autogen Microsoft\u0026rsquo;s multi-agent framework for goal-oriented dialogue and tasks. Python Conversational multi-agent systems LangChain General framework for chaining LLMs with tools, memory, and logic. Python Broader LLM apps beyond just agents AgentOps Infra layer for deploying and monitoring agentic systems. Platform Scaling, testing, and managing agents Why I Chose LangGraph For this project, I chose LangGraph because:\nIt gives clear control over the order of steps It fits pipelines like: get YouTube URL → fetch transcript → summarize → answer questions It supports state, so each step can use output from the previous one YouTube Video Q\u0026amp;A Agent This project uses one agent that does two things:\nSummarize the video transcript Answer questions about the video content Responsibilities Extract the transcript from a YouTube video If the user asks for a summary, generate a short summary If the user asks a question, answer it using the transcript as context Use Google\u0026rsquo;s gemini-2.0-flash model through langchain-google-genai How It Works Transcript extraction\nThe agent fetches the transcript with youtube-transcript-api and cleans the text.\nRouting based on user input\nIf the user types \u0026quot;summary\u0026quot;, the full transcript is sent to Gemini with a summary prompt If the user asks a question, both the transcript and the question are sent to Gemini Response\nGemini returns either a summary or an answer, which is shown to the user.\nTechnologies Used langchain-google-genai — connects to Gemini youtube-transcript-api — fetches video transcripts LangGraph — runs the workflow dotenv — loads the API key from a .env file Example Workflow User enters YouTube URL → Transcript fetched → User enters \u0026#34;summary\u0026#34; → Gemini generates a summary OR User enters a question → Gemini answers using the transcript Source Code Full project on GitHub:\nhttps://github.com/ayyzenn/youtube-video-qa.git\n","permalink":"https://ayyzenn.github.io/posts/agent/","summary":"\u003ch2 id=\"what-i-built\"\u003eWhat I Built\u003c/h2\u003e\n\u003cp\u003eI wanted to paste a YouTube URL and get a summary — or ask questions about the video — without watching the whole thing. This project uses \u003cstrong\u003eLangGraph\u003c/strong\u003e for the workflow and \u003cstrong\u003eGemini\u003c/strong\u003e for the actual summarizing and Q\u0026amp;A.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"key-features-of-agentic-workflows\"\u003eKey Features of Agentic Workflows\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eModularity\u003c/strong\u003e — each step can be built and tested on its own\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eState\u003c/strong\u003e — data flows from step to step in a structured way\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eReusability\u003c/strong\u003e — the same steps can be reused in other projects\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAutonomy\u003c/strong\u003e — each step can decide what to do based on the input it receives\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"popular-tools-for-agentic-workflows\"\u003ePopular Tools for Agentic Workflows\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eTool\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eDescription\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eLanguage\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eBest For\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eLangGraph\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eGraph-based AI workflow framework with state management.\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePython\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eCustom step-by-step pipelines\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eCrewAI\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eAgent orchestration tool inspired by human teams and role delegation.\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePython\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eRole-based agent collaboration\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eAutogen\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMicrosoft\u0026rsquo;s multi-agent framework for goal-oriented dialogue and tasks.\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePython\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eConversational multi-agent systems\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eLangChain\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eGeneral framework for chaining LLMs with tools, memory, and logic.\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePython\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eBroader LLM apps beyond just agents\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eAgentOps\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eInfra layer for deploying and monitoring agentic systems.\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePlatform\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eScaling, testing, and managing agents\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr\u003e\n\u003ch2 id=\"why-i-chose-langgraph\"\u003eWhy I Chose LangGraph\u003c/h2\u003e\n\u003cp\u003eFor this project, I chose \u003cstrong\u003eLangGraph\u003c/strong\u003e because:\u003c/p\u003e","title":"YouTube Summarizer: What I Built with LangGraph and Gemini"},{"content":"Why I Use Ollama I wanted to run LLMs on my own hardware without paying for API calls or sending data to the cloud. Ollama made that straightforward — install, pull a model, start chatting.\nInstall Ollama Arch Linux Update your system, then install Ollama:\nsudo pacman -Syu sudo pacman -S ollama Enable and start the service:\nsudo systemctl enable ollama sudo systemctl start ollama sudo systemctl status ollama You should see active (running).\nUbuntu Install Ollama with the official install script:\ncurl -fsSL https://ollama.com/install.sh | sh Start the service:\nsudo systemctl enable ollama sudo systemctl start ollama sudo systemctl status ollama Available Models Browse models at ollama.com/library.\nPopular choices:\ndeepseek-r1 — strong reasoning and coding help llama3 — general-purpose chat mistral — fast and lightweight gemma — small models for weaker hardware Pull and Run DeepSeek This example uses deepseek-r1. You can swap the name for any model you prefer.\nStep 1: Download the model ollama pull deepseek-r1 Step 2: Confirm it is installed ollama list You should see deepseek-r1 in the list.\nStep 3: Start a chat session ollama run deepseek-r1 Type a question and press Enter. Type /bye to exit.\nSimple Test: Generate Python Code Ask the model to write a short program:\necho \u0026#34;Write a Python program to calculate factorial using recursion.\u0026#34; | ollama run deepseek-r1 Example output:\ndef factorial(n): if n == 0: return 1 return n * factorial(n - 1) num = int(input(\u0026#34;Enter a number: \u0026#34;)) print(\u0026#34;Factorial:\u0026#34;, factorial(num)) Troubleshooting Problem What to try command not found: ollama Install Ollama first (see above) Service not running sudo systemctl restart ollama Model download fails Check your internet connection Slow responses Try a smaller model like gemma Conclusion Ollama makes it easy to run LLMs locally. Install it once, pull a model, and start chatting from your terminal — on Arch Linux or Ubuntu.\n","permalink":"https://ayyzenn.github.io/posts/ollama/","summary":"\u003ch2 id=\"why-i-use-ollama\"\u003eWhy I Use Ollama\u003c/h2\u003e\n\u003cp\u003eI wanted to run LLMs on my own hardware without paying for API calls or sending data to the cloud. \u003cstrong\u003eOllama\u003c/strong\u003e made that straightforward — install, pull a model, start chatting.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"install-ollama\"\u003eInstall Ollama\u003c/h2\u003e\n\u003ch3 id=\"arch-linux\"\u003eArch Linux\u003c/h3\u003e\n\u003cp\u003eUpdate your system, then install Ollama:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo pacman -Syu\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo pacman -S ollama\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEnable and start the service:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl enable ollama\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl start ollama\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl status ollama\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou should see \u003ccode\u003eactive (running)\u003c/code\u003e.\u003c/p\u003e","title":"Ollama + DeepSeek: How I Run LLMs Locally"},{"content":"Why I Installed Gentoo Processor: Intel Core i5 4th Gen (4 cores) RAM: 8GB Storage: 256GB SSD I’ll be using the Minimal ISO because I like full control over my system, but if you prefer a GUI installer, that’s totally fine too!\nStep 1: Grab the Gentoo ISO Head over to Gentoo’s official website and download the ISO. You’ll see two options:\nGUI Installer (if you want an easier time) Minimal (CLI) Installer (if you want to go full pro mode) For this guide, we’re going Minimal. Let’s get our hands dirty!\nStep 2: Setting Up the Disk Check your available disks:\nlsblk Partition your disk:\ncfdisk /dev/sda Format the partitions:\nmkfs.fat -F 32 /dev/sda1 mkfs.ext4 /dev/sda2 Mount the partitions:\nmount /dev/sda2 /mnt/gentoo mkdir -p /mnt/gentoo/boot mount /dev/sda1 /mnt/gentoo/boot Step 3: Getting Stage 3 Ready Download the latest Stage 3 tarball:\nlinks https://www.gentoo.org/downloads/mirrors/ Extract it:\ntar xpvf stage3-*.tar.xz --xattrs-include=\u0026#39;*.*\u0026#39; --numeric-owner -C /mnt/gentoo Copy your network settings:\ncp --dereference /etc/resolv.conf /mnt/gentoo/etc/ Step 4: Mounting System Directories mount -t proc /proc /mnt/gentoo/proc mount --rbind /sys /mnt/gentoo/sys mount --make-rslave /mnt/gentoo/sys mount --rbind /dev /mnt/gentoo/dev mount --make-rslave /mnt/gentoo/dev Step 5: Entering the Gentoo World chroot /mnt/gentoo /bin/bash source /etc/profile export PS1=\u0026#34;(chroot) ${PS1}\u0026#34; Boom! You’re now inside Gentoo. Feels good, right?\nStep 6: Syncing and Configuring the System First, sync the package manager:\nemerge --sync Pick a profile:\neselect profile list eselect profile set 23 # Choose whatever fits your needs Tweak your make.conf file:\nnano /etc/portage/make.conf Example settings:\nCOMMON_FLAGS=\u0026#34;-march=native -O2 -pipe\u0026#34; Update everything:\nemerge --sync emerge -vuDN @world # Grab a coffee, this takes a while (12hrs for me) Clean up the mess:\nemerge --depclean Set up pinentry:\neselect pinentry set pinentry-gnome3 Step 7: Installing Kernel emerge -v =sys-kernel/gentoo-sources-6.12.16 emerge sys-boot/grub app-admin/sysklogd sys-process/cronie app-admin/sudo app-misc/neofetch Grant necessary permissions:\necho \u0026#34;sys-kernel/linux-firmware linux-fw-redistributable\u0026#34; \u0026gt;\u0026gt; /etc/portage/package.license Install Genkernel:\nemerge sys-kernel/genkernel ln -sf /usr/src/linux-6.12.16-gentoo /usr/src/linux genkernel all Step 8: Fstab Configuration Edit /etc/fstab:\nnano /etc/fstab Set up your partitions with UUIDs (find them using blkid):\nUUID=\u0026lt;your-root-uuid\u0026gt; / ext4 defaults 1 1 UUID=\u0026lt;your-boot-uuid\u0026gt; /boot vfat defaults 1 2 Test it:\nmount -a Step 9: Installing and Configuring the Bootloader grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=Gentoo grub-mkconfig -o /boot/grub/grub.cfg Set your hostname:\necho \u0026#34;gentoo\u0026#34; \u0026gt; /etc/hostname Set a root password:\npasswd Step 10: Creating a User Account Create a user:\ngroupadd mail mkdir -p /var/spool/mail touch /var/spool/mail/yourusername chmod 660 /var/spool/mail/yourusername useradd -m -G mail -s /bin/bash yourusername passwd yourusername chown yourusername:mail /var/spool/mail/yourusername Give them sudo access:\nEDITOR=nano visudo Uncomment this line:\n%wheel ALL=(ALL:ALL) ALL Set up their home directory:\nmkdir -p /home/yourusername chown yourusername:users /home/yourusername chmod 700 /home/yourusername Test your new user:\nsu - yourusername exit # exit from your new user Step 11: Enabling Important Services rc-update add sysklogd default rc-update add cronie default Step 12: Final Steps and Reboot Exit chroot and unmount everything:\nexit umount -l /mnt/gentoo/dev{/shm,/pts,} umount -R /mnt/gentoo or umount -l /mnt/gentoo # if the drive is busy And finally, the moment of truth:\nreboot Conclusion Congratulations! You just installed Gentoo Linux from scratch. Enjoy your custom-built system.\nIf you hit any snags, check the Gentoo Handbook or the forums.\n","permalink":"https://ayyzenn.github.io/posts/gentoo/","summary":"\u003ch2 id=\"why-i-installed-gentoo\"\u003eWhy I Installed Gentoo\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eProcessor:\u003c/strong\u003e Intel Core i5 4th Gen (4 cores)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRAM:\u003c/strong\u003e 8GB\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStorage:\u003c/strong\u003e 256GB SSD\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI’ll be using the \u003cstrong\u003eMinimal ISO\u003c/strong\u003e because I like full control over my system, but if you prefer a GUI installer, that’s totally fine too!\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"step-1-grab-the-gentoo-iso\"\u003eStep 1: Grab the Gentoo ISO\u003c/h2\u003e\n\u003cp\u003eHead over to \u003ca href=\"https://www.gentoo.org/\"\u003eGentoo’s official website\u003c/a\u003e and download the ISO. You’ll see two options:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eGUI Installer\u003c/strong\u003e (if you want an easier time)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMinimal (CLI) Installer\u003c/strong\u003e (if you want to go full pro mode)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eFor this guide, we’re going \u003cstrong\u003eMinimal\u003c/strong\u003e. Let’s get our hands dirty!\u003c/p\u003e","title":"Gentoo Install: What I Did on My Laptop"},{"content":"What I Did I installed Odoo (open-source ERP) on Ubuntu for a project. These are the exact steps I ran — all apt commands on Ubuntu.\nsudo apt update \u0026amp;\u0026amp; sudo apt upgrade Create an Odoo User Run this command to create a system user for Odoo:\nsudo adduser -system -home=/opt/odoo -group odoo Install PostgreSQL Odoo needs PostgreSQL as its database, so install it with:\nsudo apt-get install postgresql -y Create a PostgreSQL User for Odoo sudo su - postgres -c \u0026#34;createuser -s odoo\u0026#34; 2\u0026gt; /dev/null || true Install Python Dependencies Odoo requires some Python packages. Install them with:\nsudo apt-get install libpq-dev python-dev libxml2-dev libxslt1-dev libldap2-dev libsasl2-dev libffi-dev sudo -H pip3 install -r https://raw.githubusercontent.com/odoo/odoo/master/requirements.txt Install Other Required Packages You\u0026rsquo;ll need Node.js and npm:\nsudo apt-get install nodejs npm -y sudo npm install -g rtlcss Install Wkhtmltopdf Odoo uses Wkhtmltopdf to generate PDFs. Install it with:\nsudo apt-get install xfonts-75dpi sudo wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.bionic_amd64.deb sudo dpkg -i wkhtmltox_0.12.6-1.bionic_amd64.deb sudo cp /usr/local/bin/wkhtmltoimage /usr/bin/wkhtmltoimage sudo cp /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf Set Up Logging Create a log directory so Odoo can store its logs:\nsudo mkdir /var/log/odoo sudo chown odoo:odoo /var/log/odoo Install Odoo Get Odoo from GitHub:\nsudo apt-get install git sudo git clone --depth 1 --branch 14.0 https://www.github.com/odoo/odoo /odoo/odoo-server Set Permissions Give Odoo ownership of its files:\nsudo chown -R odoo:odoo /odoo/* Create a Config File Set up Odoo\u0026rsquo;s configuration:\nsudo touch /etc/odoo-server.conf sudo su root -c \u0026#34;printf \u0026#39;[options] \\n; This is the password that allows database operations:\\n\u0026#39; \u0026gt;\u0026gt; /etc/odoo-server.conf\u0026#34; sudo su root -c \u0026#34;printf \u0026#39;admin_passwd = admin\\n\u0026#39; \u0026gt;\u0026gt; /etc/odoo-server.conf\u0026#34; sudo su root -c \u0026#34;printf \u0026#39;xmlrpc_port = 8069\\n\u0026#39; \u0026gt;\u0026gt; /etc/odoo-server.conf\u0026#34; sudo su root -c \u0026#34;printf \u0026#39;logfile = /var/log/odoo/odoo-server.log\\n\u0026#39; \u0026gt;\u0026gt; /etc/odoo-server.conf\u0026#34; sudo su root -c \u0026#34;printf \u0026#39;addons_path=/odoo/odoo-server/addons\\n\u0026#39; \u0026gt;\u0026gt; /etc/odoo-server.conf\u0026#34; sudo chown odoo:odoo /etc/odoo-server.conf sudo chmod 640 /etc/odoo-server.conf Start Odoo Finally, let\u0026rsquo;s fire it up!\nsudo su - odoo -s /bin/bash cd /odoo/odoo-server ./odoo-bin -c /etc/odoo-server.conf Now your Odoo instance is up and running! Open your browser and go to:\nlocalhost:8069 ","permalink":"https://ayyzenn.github.io/posts/odoo/","summary":"\u003ch2 id=\"what-i-did\"\u003eWhat I Did\u003c/h2\u003e\n\u003cp\u003eI installed \u003cstrong\u003eOdoo\u003c/strong\u003e (open-source ERP) on Ubuntu for a project. These are the exact steps I ran — all \u003ccode\u003eapt\u003c/code\u003e commands on Ubuntu.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo apt upgrade\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"create-an-odoo-user\"\u003eCreate an Odoo User\u003c/h2\u003e\n\u003cp\u003eRun this command to create a system user for Odoo:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo adduser -system -home\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e/opt/odoo -group odoo\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"install-postgresql\"\u003eInstall PostgreSQL\u003c/h2\u003e\n\u003cp\u003eOdoo needs PostgreSQL as its database, so install it with:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt-get install postgresql -y\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"create-a-postgresql-user-for-odoo\"\u003eCreate a PostgreSQL User for Odoo\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo su - postgres -c \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;createuser -s odoo\u0026#34;\u003c/span\u003e 2\u0026gt; /dev/null \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"install-python-dependencies\"\u003eInstall Python Dependencies\u003c/h2\u003e\n\u003cp\u003eOdoo requires some Python packages. Install them with:\u003c/p\u003e","title":"Odoo on Ubuntu: How I Installed It"},{"content":"What I Set Up I needed a simple FTP server on Ubuntu for file transfers. ProFTPD did the job. This is my setup — on Arch you\u0026rsquo;d install with sudo pacman -S proftpd and edit the same config path.\nPlain FTP is not encrypted. For anything serious, use SFTP over SSH instead.\nInstalling ProFTPD Getting ProFTPD set up is pretty simple. Just run:\nsudo apt-get install proftpd Configuring ProFTPD Once it\u0026rsquo;s installed, you\u0026rsquo;ll need to tweak the config file. Open it up with:\nsudo nano /etc/proftpd/proftpd.conf Update the ServerName field to match your hostname:\nServerName \u0026#34;test.com\u0026#34; Also, uncomment this line to make sure users are restricted to their home directories:\n# Use this to jail all users in their homes DefaultRoot /home/ayyzenn/ Once you\u0026rsquo;re done, save the file and restart ProFTPD so the changes take effect:\nsudo service proftpd restart Accessing the FTP Server Now that everything is set up, you can connect to your FTP server from the command line:\nftp test.com To exit, just type:\nexit Adding a User If you want another user to access the FTP server, you\u0026rsquo;ll need to add them first. Run:\nsudo adduser \u0026lt;username\u0026gt; Follow the prompts—it’s fine to leave the details blank if you want.\nThen, make sure they have the right permissions by adding them to the sudo group:\nsudo usermod -aG sudo username Installing FileZilla If you prefer a GUI, you can use FileZilla. Install it with:\nsudo apt install filezilla Connecting to Your FTP Server with FileZilla Open FileZilla. Enter your: Hostname or IP address Username Password Port number (default: 21) Click Quickconnect. Accept any prompts and you\u0026rsquo;re good to go! Wrapping Up That\u0026rsquo;s it! You now have a working ProFTPD server. Whether you\u0026rsquo;re using the command line or FileZilla, you can easily manage file transfers between your systems. Enjoy!\n","permalink":"https://ayyzenn.github.io/posts/proftpd/","summary":"\u003ch2 id=\"what-i-set-up\"\u003eWhat I Set Up\u003c/h2\u003e\n\u003cp\u003eI needed a simple FTP server on Ubuntu for file transfers. \u003cstrong\u003eProFTPD\u003c/strong\u003e did the job. This is my setup — on Arch you\u0026rsquo;d install with \u003ccode\u003esudo pacman -S proftpd\u003c/code\u003e and edit the same config path.\u003c/p\u003e\n\u003cp\u003ePlain FTP is \u003cstrong\u003enot encrypted\u003c/strong\u003e. For anything serious, use SFTP over SSH instead.\u003c/p\u003e\n\u003ch2 id=\"installing-proftpd\"\u003eInstalling ProFTPD\u003c/h2\u003e\n\u003cp\u003eGetting ProFTPD set up is pretty simple. Just run:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt-get install proftpd\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"configuring-proftpd\"\u003eConfiguring ProFTPD\u003c/h2\u003e\n\u003cp\u003eOnce it\u0026rsquo;s installed, you\u0026rsquo;ll need to tweak the config file. Open it up with:\u003c/p\u003e","title":"ProFTPD on Ubuntu: Setting Up FTP"},{"content":"Why I Looked Into Chef Managing servers one by one doesn\u0026rsquo;t scale. I set up Chef to store server configs as code — a Chef server, a workstation where I write recipes, and a node that pulls its config automatically.\nNote: I did this on Ubuntu 18.04 using .deb packages and apt.\nChef Server The Chef server acts as the central hub for all workstations and nodes under Chef management. Configuration changes made on workstations are pushed to the Chef server, where they are pulled by nodes using chef-client to apply the configurations.\nInstall the Chef Server Download the latest Chef server core:\nwget https://packages.chef.io/files/stable/chef-server/13.1.13/ubuntu/18.04/chef-server-core_13.1.13-1_amd64.deb Install the server:\nsudo dpkg -i chef-server-core_*.deb Start the Chef server services:\nsudo chef-server-ctl reconfigure Create a Chef User and Organization To link workstations and nodes to the Chef server, create an administrator and organization with associated RSA private keys.\nCreate a .chef directory to store the keys:\nmkdir ~/.chef Create a user with chef-server-ctl, replacing placeholders with your details:\nsudo chef-server-ctl user-create USER_NAME FIRST_NAME LAST_NAME EMAIL \u0026#39;PASSWORD\u0026#39; --filename ~/.chef/USER_NAME.pem List all users:\nsudo chef-server-ctl user-list Create an organization and associate the user:\nsudo chef-server-ctl org-create ORG_NAME \u0026#34;ORG_FULL_NAME\u0026#34; --association_user USER_NAME --filename ~/.chef/ORG_NAME.pem List all organizations:\nsudo chef-server-ctl org-list With the Chef server installed and RSA keys generated, the workstation can now be configured.\nChef Workstations The Chef workstation is where recipes, cookbooks, attributes, and configurations are managed. It can be a local machine or a remote server.\nSetting Up a Workstation Download the latest Chef Workstation:\nwget https://packages.chef.io/files/stable/chef-workstation/0.2.43/ubuntu/18.04/chef-workstation_0.2.43-1_amd64.deb Install Chef Workstation:\nsudo dpkg -i chef-workstation_*.deb Create your Chef repository:\nchef generate repo chef-repo Ensure that your workstation’s /etc/hosts file correctly maps IP addresses to the Chef server and workstation hostnames.\nCreate a .chef subdirectory within chef-repo to store authentication files:\nmkdir ~/chef-repo/.chef cd chef-repo Add the RSA Private Keys Authentication between the Chef server and workstations/nodes is performed using public-key encryption. Copy the RSA private keys from the Chef server to the workstation.\nGenerate an RSA key pair on the workstation (if not already available):\nssh-keygen -b 4096 Upload the workstation’s public key to the Chef server:\nssh-copy-id example_user@192.0.2.0 Copy the .pem files from the Chef server to the workstation:\nscp example_user@192.0.2.0:~/.chef/*.pem ~/chef-repo/.chef/ Confirm the files were copied:\nls ~/chef-repo/.chef Bootstrap a Node Bootstrapping a node installs the Chef client and validates the node, allowing it to pull and apply configurations from the Chef server.\nUpdate the /etc/hosts file on the node to correctly identify the node, Chef server, and workstation.\nFrom the workstation, navigate to the .chef directory:\ncd ~/chef-repo/.chef Bootstrap the node:\nknife bootstrap 192.0.2.0 -x root -P password --node-name nodename Verify the node has been bootstrapped:\nknife client list Add the bootstrapped node to the workstation’s /etc/hosts file.\nFor more detailed information, refer to the Linode guide.\n","permalink":"https://ayyzenn.github.io/posts/chef/","summary":"\u003ch2 id=\"why-i-looked-into-chef\"\u003eWhy I Looked Into Chef\u003c/h2\u003e\n\u003cp\u003eManaging servers one by one doesn\u0026rsquo;t scale. I set up \u003cstrong\u003eChef\u003c/strong\u003e to store server configs as code — a Chef server, a workstation where I write recipes, and a node that pulls its config automatically.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNote:\u003c/strong\u003e I did this on \u003cstrong\u003eUbuntu 18.04\u003c/strong\u003e using \u003ccode\u003e.deb\u003c/code\u003e packages and \u003ccode\u003eapt\u003c/code\u003e.\u003c/p\u003e\n\u003ch2 id=\"chef-server\"\u003eChef Server\u003c/h2\u003e\n\u003cp\u003eThe Chef server acts as the central hub for all workstations and nodes under Chef management. Configuration changes made on workstations are pushed to the Chef server, where they are pulled by nodes using \u003ccode\u003echef-client\u003c/code\u003e to apply the configurations.\u003c/p\u003e","title":"Chef Server Setup: What I Did on Ubuntu"},{"content":"What I Built I set up a home VoIP system using Asterisk as the server and Twinkle as the softphone client. Everything here runs on Ubuntu — same network, two extensions, calls between machines.\nHow VoIP Works VoIP sends your voice as data over the internet instead of through a phone line. Cheaper than landlines, especially for long-distance — that\u0026rsquo;s why I tried it.\nAsterisk as the Server Asterisk is the PBX — it handles call routing between extensions. I compiled it from source on Ubuntu.\nInstalling Asterisk Here\u0026rsquo;s what I ran:\nsudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y Step 2: Install Essential Dependencies Asterisk requires a bunch of dependencies to function properly. Install them all in one go:\nsudo apt install -y gnupg2 software-properties-common git curl wget \\ libnewt-dev libssl-dev libncurses5-dev autoconf subversion \\ libsqlite3-dev build-essential libjansson-dev libxml2-dev pkg-config \\ libtool uuid-dev Step 3: Install DAHDI and LibPRI (For Extra Features) If you want better telephony support, install DAHDI and LibPRI:\ncd /usr/src/ sudo git clone -b next git://git.asterisk.org/dahdi/linux dahdi-linux \u0026amp;\u0026amp; \\ cd dahdi-linux \u0026amp;\u0026amp; sudo make \u0026amp;\u0026amp; sudo make install cd /usr/src/ sudo git clone -b next git://git.asterisk.org/dahdi/tools dahdi-tools \u0026amp;\u0026amp; \\ cd dahdi-tools \u0026amp;\u0026amp; sudo autoreconf -i \u0026amp;\u0026amp; sudo ./configure \u0026amp;\u0026amp; \\ sudo make install \u0026amp;\u0026amp; sudo make install-config \u0026amp;\u0026amp; sudo dahdi_genconf modules cd /usr/src/ sudo git clone https://gerrit.asterisk.org/libpri libpri \u0026amp;\u0026amp; \\ cd libpri \u0026amp;\u0026amp; sudo make \u0026amp;\u0026amp; sudo make install Step 4: Install Asterisk Now for the main event—installing Asterisk itself:\ncd /usr/src/ sudo git clone -b 18 https://gerrit.asterisk.org/asterisk asterisk-18 \u0026amp;\u0026amp; \\ cd asterisk-18/ \u0026amp;\u0026amp; sudo contrib/scripts/get_mp3_source.sh \u0026amp;\u0026amp; \\ sudo contrib/scripts/install_prereq install \u0026amp;\u0026amp; sudo ./configure \u0026amp;\u0026amp; \\ sudo make menuselect \u0026amp;\u0026amp; sudo make -j2 \u0026amp;\u0026amp; \\ sudo make install \u0026amp;\u0026amp; sudo make samples \u0026amp;\u0026amp; sudo make config \u0026amp;\u0026amp; sudo ldconfig Step 5: Create Asterisk User It\u0026rsquo;s best to run Asterisk under a dedicated user:\nsudo adduser --system --group --home /var/lib/asterisk --no-create-home --gecos \u0026#34;Asterisk PBX\u0026#34; asterisk sudo usermod -a -G dialout,audio asterisk sudo chown -R asterisk: /var/{lib,log,run,spool}/asterisk /usr/lib/asterisk /etc/asterisk sudo chmod -R 750 /var/{lib,log,run,spool}/asterisk /usr/lib/asterisk /etc/asterisk Step 6: Configure Asterisk extensions.conf Edit the extensions.conf file:\nsudo nano /etc/asterisk/extensions.conf Add the following:\n[from-internal] exten = 100,1,Answer() same = n,Wait(1) same = n,Playback(hello-world) same = n,Hangup() sip.conf Edit the sip.conf file:\nsudo nano /etc/asterisk/sip.conf Add these configurations:\n[general] context=default [6001] type=friend context=from-internal host=dynamic secret=temp123 disallow=all allow=ulaw [6002] type=friend context=from-internal host=dynamic secret=temp123 disallow=all allow=ulaw Step 7: Configure Firewall for Asterisk Don\u0026rsquo;t forget to allow VoIP traffic through the firewall:\nsudo ufw allow 5060/udp sudo ufw allow 10000:20000/udp Step 8: Start Asterisk Everything is set! Start Asterisk and check its status:\nsudo systemctl start asterisk sudo asterisk -vvvr Installing Twinkle (Softphone) To test our Asterisk setup, install the Twinkle softphone:\nsudo apt install -y twinkle Configuring Twinkle Open Twinkle and select \u0026ldquo;Wizard\u0026rdquo;. Enter a profile name. Fill out the configuration form (use temp123 as the password, as set in sip.conf). Save and confirm the settings. Use the main menu to place calls. For testing, install Twinkle on a second system and configure it with 6002 instead of 6001. Ensure both clients are on the same network.\nThat\u0026rsquo;s it! You now have a working VoIP setup using Asterisk and Twinkle.\n","permalink":"https://ayyzenn.github.io/posts/voip/","summary":"\u003ch2 id=\"what-i-built\"\u003eWhat I Built\u003c/h2\u003e\n\u003cp\u003eI set up a home VoIP system using \u003cstrong\u003eAsterisk\u003c/strong\u003e as the server and \u003cstrong\u003eTwinkle\u003c/strong\u003e as the softphone client. Everything here runs on \u003cstrong\u003eUbuntu\u003c/strong\u003e — same network, two extensions, calls between machines.\u003c/p\u003e\n\u003ch2 id=\"how-voip-works\"\u003eHow VoIP Works\u003c/h2\u003e\n\u003cp\u003eVoIP sends your voice as data over the internet instead of through a phone line. Cheaper than landlines, especially for long-distance — that\u0026rsquo;s why I tried it.\u003c/p\u003e\n\u003ch2 id=\"asterisk-as-the-server\"\u003eAsterisk as the Server\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eAsterisk\u003c/strong\u003e is the PBX — it handles call routing between extensions. I compiled it from source on Ubuntu.\u003c/p\u003e","title":"VoIP with Asterisk: My Home Phone Setup on Ubuntu"},{"content":"What I Did I wanted to try Puppet without dedicating two full VMs. I ran a Puppet master and agent in separate Ubuntu Docker containers on my machine. Docker host commands work the same on Ubuntu and Arch.\nInstalling Puppet Master Download the Ubuntu image:\nsudo docker pull ubuntu Create a container for Puppet master:\nsudo docker run --name puppet-master -it ubuntu exit Start the container:\nsudo docker start puppet-master Open the bash shell of the Puppet master container:\nsudo docker exec -it puppet-master bash Update and upgrade the container:\napt update apt upgrade Install necessary tools:\napt install nano inetutils-ping net-tools wget -y Add the Puppet repository:\nwget https://apt.puppetlabs.com/puppet7-release-focal.deb dpkg -i puppet7-release-focal.deb apt update Create an image of the configured container:\nexit sudo docker commit puppet-master puppet-7 Confirm the image creation:\nsudo docker images Install Puppet server:\nsudo docker exec -it puppet-master bash apt install puppetserver Modify memory allocation for Java in Puppet server configuration:\nnano /etc/default/puppetserver Replace:\nJAVA_ARGS=\u0026#34;-Xms2g -Xmx2g\u0026#34; With:\nJAVA_ARGS=\u0026#34;-Xms64m -Xmx512m\u0026#34; Create a certificate authority:\n/opt/puppetlabs/bin/puppetserver ca setup Start the Puppet server:\nservice puppetserver start Add Puppet to the system path:\necho \u0026#39;PATH=$PATH:/opt/puppetlabs/bin\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc Retrieve the fully qualified domain name (FQDN):\nfacter -p | grep fqdn Installing the Puppet Agent Create a container for the Puppet agent using the previously created image:\nsudo docker run --name puppet-agent -it puppet-7 exit Start the Puppet agent container:\nsudo docker start puppet-agent Open the bash shell of the Puppet agent container:\nsudo docker exec -it puppet-agent bash Install Puppet agent:\napt install puppet-agent Add Puppet to the system path:\necho \u0026#39;PATH=$PATH:/opt/puppetlabs/bin\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc Configuring Puppet Master and Puppet Agent Configuring the Puppet Agent Edit the hosts file:\nnano /etc/hosts Add the IP address of the Puppet master container along with its FQDN.\nCheck the connection:\nping \u0026lt;your fqdn\u0026gt; Configuring the Puppet Master Sign certificates of all agents:\nnano /etc/puppetlabs/puppet/puppet.conf Add the following line:\nautosign=true service puppetserver restart Verify the connection:\npuppet agent --test --server \u0026lt;fqdn\u0026gt; If the connection is established successfully, the output should indicate no errors.\n","permalink":"https://ayyzenn.github.io/posts/puppet/","summary":"\u003ch2 id=\"what-i-did\"\u003eWhat I Did\u003c/h2\u003e\n\u003cp\u003eI wanted to try \u003cstrong\u003ePuppet\u003c/strong\u003e without dedicating two full VMs. I ran a Puppet master and agent in separate \u003cstrong\u003eUbuntu Docker containers\u003c/strong\u003e on my machine. Docker host commands work the same on Ubuntu and Arch.\u003c/p\u003e\n\u003ch2 id=\"installing-puppet-master\"\u003eInstalling Puppet Master\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eDownload the Ubuntu image:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo docker pull ubuntu\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCreate a container for Puppet master:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo docker run --name puppet-master -it ubuntu\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexit\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eStart the container:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo docker start puppet-master\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eOpen the bash shell of the Puppet master container:\u003c/p\u003e","title":"Puppet with Docker: How I Tested Master and Agent"},{"content":"What I Did Here After installing Docker, I wanted to run an actual Ubuntu container — not just hello-world. The steps inside this post are the same on Arch Linux and Ubuntu hosts. Only the Docker installation differs — see Docker on Arch Linux and Ubuntu for that part.\nArch Linux (host) Make sure Docker is installed and running on your Arch machine first:\nsudo pacman -S docker sudo systemctl enable --now docker systemctl status docker Pull the Ubuntu image sudo docker pull ubuntu Create and enter a container sudo docker run --name my-container -it ubuntu What those flags mean:\n-i — keep input open (interactive) -t — give me a terminal inside the container --name my-container — name it so I can find it later Your shell prompt changes — you are inside the container now.\nUpdate packages inside the container You are root inside the container, so no sudo:\napt update apt upgrade -y Useful commands (from the Arch host) Run these from your normal Arch terminal, not inside the container:\n# List running containers sudo docker ps # List all containers (including stopped) sudo docker ps -a # Stop the container sudo docker stop my-container # Start it again sudo docker start my-container # Re-enter a running container sudo docker exec -it my-container bash Ubuntu (host) Make sure Docker is installed and running on your Ubuntu machine first:\nsudo apt update sudo apt install docker.io sudo systemctl start docker sudo systemctl enable docker systemctl status docker Pull the Ubuntu image sudo docker pull ubuntu Create and enter a container sudo docker run --name my-container -it ubuntu Same flags as above — after this, you are inside the container.\nUpdate packages inside the container apt update apt upgrade -y Useful commands (from the Ubuntu host) # List running containers sudo docker ps # List all containers (including stopped) sudo docker ps -a # Stop the container sudo docker stop my-container # Start it again sudo docker start my-container # Re-enter a running container sudo docker exec -it my-container bash What I Learned Docker containers are temporary by default — remove one and the changes inside are gone unless you commit it to a new image or use volumes. For learning though, docker run -it ubuntu on either Arch or Ubuntu is the simplest way to get started.\nThe container itself runs Ubuntu Linux regardless of whether your host is Arch or Ubuntu — that is the whole point of containers.\n","permalink":"https://ayyzenn.github.io/posts/container_docker/","summary":"\u003ch2 id=\"what-i-did-here\"\u003eWhat I Did Here\u003c/h2\u003e\n\u003cp\u003eAfter installing Docker, I wanted to run an actual Ubuntu container — not just \u003ccode\u003ehello-world\u003c/code\u003e. The steps inside this post are the same on \u003cstrong\u003eArch Linux\u003c/strong\u003e and \u003cstrong\u003eUbuntu\u003c/strong\u003e hosts. Only the Docker \u003cem\u003einstallation\u003c/em\u003e differs — see \u003ca href=\"/posts/dockers/\"\u003eDocker on Arch Linux and Ubuntu\u003c/a\u003e for that part.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"arch-linux-host\"\u003eArch Linux (host)\u003c/h2\u003e\n\u003cp\u003eMake sure Docker is installed and running on your Arch machine first:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo pacman -S docker\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo systemctl enable --now docker\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esystemctl status docker\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"pull-the-ubuntu-image\"\u003ePull the Ubuntu image\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo docker pull ubuntu\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"create-and-enter-a-container\"\u003eCreate and enter a container\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo docker run --name my-container -it ubuntu\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhat those flags mean:\u003c/p\u003e","title":"Creating My First Docker Container"},{"content":"Why I Started Using Docker I wanted a way to run apps without messing up my main system. Docker packs an app with everything it needs into a container — a small, isolated environment that shares my kernel but stays separate from everything else.\nIf something breaks inside a container, I delete it and start over. No reinstalling the whole OS.\nContainers vs Images (Quick Version) Image — a read-only template (like a recipe) Container — a running instance of that image (like the actual dish you cooked) You build or pull an image once. You can spin up as many containers from it as you want.\nArch Linux This is what I use on my main machine now.\nInstall Docker sudo pacman -S docker sudo systemctl enable --now docker Check the service is running:\nsystemctl status docker You should see active (running).\nTest with hello-world sudo docker pull hello-world sudo docker images sudo docker run hello-world If you see a friendly message from Docker, it works.\nRun Docker without sudo (optional) I got tired of typing sudo every time:\nsudo usermod -aG docker $USER Log out and log back in. Then test:\ndocker ps If that works without sudo, you\u0026rsquo;re set.\nUbuntu This is what I used first when I was learning Docker on Ubuntu 22.04.\nInstall Docker sudo apt update sudo apt install docker.io sudo systemctl start docker sudo systemctl enable docker Check the service is running:\nsystemctl status docker You should see active (running).\nTest with hello-world sudo docker pull hello-world sudo docker images sudo docker run hello-world Same test as Arch — if hello-world prints a success message, Docker is working.\nRun Docker without sudo (optional) sudo usermod -aG docker $USER Log out and log back in. Then test:\ndocker ps Quick Reference Step Arch Linux Ubuntu Install sudo pacman -S docker sudo apt install docker.io Enable on boot sudo systemctl enable --now docker sudo systemctl enable docker Test sudo docker run hello-world sudo docker run hello-world Skip sudo sudo usermod -aG docker $USER sudo usermod -aG docker $USER The Docker commands (pull, run, ps, etc.) are the same on both distros once it is installed.\nWhat\u0026rsquo;s Next? Once Docker is installed, the next step is creating your own container. I wrote that up here: Creating My First Docker Container.\n","permalink":"https://ayyzenn.github.io/posts/dockers/","summary":"\u003ch2 id=\"why-i-started-using-docker\"\u003eWhy I Started Using Docker\u003c/h2\u003e\n\u003cp\u003eI wanted a way to run apps without messing up my main system. Docker packs an app with everything it needs into a \u003cstrong\u003econtainer\u003c/strong\u003e — a small, isolated environment that shares my kernel but stays separate from everything else.\u003c/p\u003e\n\u003cp\u003eIf something breaks inside a container, I delete it and start over. No reinstalling the whole OS.\u003c/p\u003e\n\u003ch2 id=\"containers-vs-images-quick-version\"\u003eContainers vs Images (Quick Version)\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eImage\u003c/strong\u003e — a read-only template (like a recipe)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eContainer\u003c/strong\u003e — a running instance of that image (like the actual dish you cooked)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou build or pull an image once. You can spin up as many containers from it as you want.\u003c/p\u003e","title":"Docker on Arch Linux and Ubuntu: How I Got It Running"},{"content":"Why I Bother With This I got tired of opening the browser every time I wanted to create a repo, check a PR, or trigger a workflow. GitHub CLI (gh) lets me do most of that from the terminal. Personal Access Tokens (PATs) are how Git authenticates when you push over HTTPS.\nThis post covers both — how I generate a PAT and how I use gh day to day.\nCreating a Personal Access Token (PAT) A PAT is basically a password for the command line.\nGitHub → profile photo → Settings Developer settings → Personal access tokens Generate new token Give it a name you\u0026rsquo;ll recognize later Set an expiration date Check repo (and any other scopes you need) Generate token — copy it now, you won\u0026rsquo;t see it again Treat it like a password. Don\u0026rsquo;t commit it to a repo or paste it in chat.\nInstall GitHub CLI Arch Linux:\nsudo pacman -S github-cli Or via AUR:\nyay -S github-cli Ubuntu:\nsudo apt update sudo apt install gh Check:\ngh --version Log In gh auth login Follow the prompts — I usually pick GitHub.com, HTTPS, and browser login.\nConfirm:\ngh auth status Commands I Actually Use Repos gh repo create my-repo --public gh repo clone username/repository gh repo fork username/repository Issues and PRs gh issue list gh issue create --title \u0026#34;Bug Report\u0026#34; --body \u0026#34;What went wrong\u0026#34; gh pr list gh pr merge 123 --squash --delete-branch GitHub Actions gh run list gh workflow run workflow-name.yml gh run view --log Git + gh Together I still use plain Git for commits and pushes. gh handles the GitHub-specific stuff.\ngit clone https://github.com/username/repository.git cd repository git add . git commit -m \u0026#34;Initial commit\u0026#34; git push origin main Then open a PR without leaving the terminal:\ngh pr create --title \u0026#34;New feature\u0026#34; --body \u0026#34;What this changes\u0026#34; Bottom Line PAT for auth, gh for GitHub tasks, Git for everything else. That\u0026rsquo;s my setup and it saves me a lot of clicking.\n","permalink":"https://ayyzenn.github.io/posts/git_tools/","summary":"\u003ch2 id=\"why-i-bother-with-this\"\u003eWhy I Bother With This\u003c/h2\u003e\n\u003cp\u003eI got tired of opening the browser every time I wanted to create a repo, check a PR, or trigger a workflow. \u003cstrong\u003eGitHub CLI\u003c/strong\u003e (\u003ccode\u003egh\u003c/code\u003e) lets me do most of that from the terminal. \u003cstrong\u003ePersonal Access Tokens (PATs)\u003c/strong\u003e are how Git authenticates when you push over HTTPS.\u003c/p\u003e\n\u003cp\u003eThis post covers both — how I generate a PAT and how I use \u003ccode\u003egh\u003c/code\u003e day to day.\u003c/p\u003e","title":"GitHub CLI and Personal Access Tokens: What I Use Daily"},{"content":"Why I Use GitHub I use Git to track changes in my code and GitHub to host it online. Git runs on my machine. GitHub is the website that stores my repos and lets me share them.\nIf you\u0026rsquo;re new: Git is the tool, GitHub is where the repos live.\nInstall Git Ubuntu:\nsudo apt update sudo apt install git Arch Linux:\nsudo pacman -S git Check it worked:\ngit --version Set Your Name and Email Git attaches this info to every commit:\ngit config --global user.name \u0026#34;Your Name\u0026#34; git config --global user.email \u0026#34;youremail@yourdomain.com\u0026#34; Verify:\ngit config --list Clone a Repo Before adding files, clone the repo you want to work on:\ngit clone https://github.com/githubusername/repository cd repository Copy the URL from your repo page on GitHub.\nAdd, Commit, Push After you add or edit files:\ngit add . git commit -m \u0026#34;Describe what you changed\u0026#34; git push origin main If your default branch is master, use that instead of main.\nGitHub will ask for your username and a Personal Access Token (PAT) — not your account password.\nThat\u0026rsquo;s It That\u0026rsquo;s the workflow I use every time: clone, edit, add, commit, push. Nothing fancy, but it gets the job done.\n","permalink":"https://ayyzenn.github.io/posts/git_cli/","summary":"\u003ch2 id=\"why-i-use-github\"\u003eWhy I Use GitHub\u003c/h2\u003e\n\u003cp\u003eI use \u003cstrong\u003eGit\u003c/strong\u003e to track changes in my code and \u003cstrong\u003eGitHub\u003c/strong\u003e to host it online. Git runs on my machine. GitHub is the website that stores my repos and lets me share them.\u003c/p\u003e\n\u003cp\u003eIf you\u0026rsquo;re new: Git is the tool, GitHub is where the repos live.\u003c/p\u003e\n\u003ch2 id=\"install-git\"\u003eInstall Git\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eUbuntu:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eArch Linux:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo pacman -S git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eCheck it worked:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit --version\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"set-your-name-and-email\"\u003eSet Your Name and Email\u003c/h2\u003e\n\u003cp\u003eGit attaches this info to every commit:\u003c/p\u003e","title":"GitHub from the Command Line: How I Push My Code"},{"content":"What I Did I needed a cloud server to test things without using my local machine. I spun up an Ubuntu 20.04 VM on Azure and connected via SSH. Here\u0026rsquo;s the exact process I followed.\nCreate the VM Log in to Azure Click Create a Resource Pick Ubuntu 20.04 LTS → Create Name the VM Under Administration Account, choose SSH Public Key Set a username and key pair name Under Inbound Port Rules, open SSH (22) and HTTP (80) Click Review and Create → Create Download the key pair file when prompted Wait for deployment, then click Go to Resource Connect via SSH cd ~/Downloads chmod 400 pair_key_name.pem ssh -i pair_key_name.pem username@your_ip Type yes when SSH asks about the fingerprint.\nLog Out logout Done That\u0026rsquo;s it — a running Ubuntu box in the cloud, reachable from my terminal. I use this kind of setup when I need a clean Linux environment for testing.\n","permalink":"https://ayyzenn.github.io/posts/azure/","summary":"\u003ch2 id=\"what-i-did\"\u003eWhat I Did\u003c/h2\u003e\n\u003cp\u003eI needed a cloud server to test things without using my local machine. I spun up an \u003cstrong\u003eUbuntu 20.04 VM\u003c/strong\u003e on Azure and connected via SSH. Here\u0026rsquo;s the exact process I followed.\u003c/p\u003e\n\u003ch2 id=\"create-the-vm\"\u003eCreate the VM\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eLog in to \u003ca href=\"https://portal.azure.com\"\u003eAzure\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eCreate a Resource\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003ePick \u003cstrong\u003eUbuntu 20.04 LTS\u003c/strong\u003e → \u003cstrong\u003eCreate\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eName the VM\u003c/li\u003e\n\u003cli\u003eUnder \u003cstrong\u003eAdministration Account\u003c/strong\u003e, choose \u003cstrong\u003eSSH Public Key\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eSet a username and key pair name\u003c/li\u003e\n\u003cli\u003eUnder \u003cstrong\u003eInbound Port Rules\u003c/strong\u003e, open \u003cstrong\u003eSSH (22)\u003c/strong\u003e and \u003cstrong\u003eHTTP (80)\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eReview and Create\u003c/strong\u003e → \u003cstrong\u003eCreate\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eDownload the \u003cstrong\u003ekey pair\u003c/strong\u003e file when prompted\u003c/li\u003e\n\u003cli\u003eWait for deployment, then click \u003cstrong\u003eGo to Resource\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"connect-via-ssh\"\u003eConnect via SSH\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd ~/Downloads\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003echmod \u003cspan style=\"color:#ae81ff\"\u003e400\u003c/span\u003e pair_key_name.pem\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003essh -i pair_key_name.pem username@your_ip\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eType \u003ccode\u003eyes\u003c/code\u003e when SSH asks about the fingerprint.\u003c/p\u003e","title":"Creating a VM on Microsoft Azure"},{"content":"Why I Use SSH SSH is how I control another machine from my terminal. I use it to manage servers, copy files, and run commands on remote systems without sitting in front of them.\nFor Windows remote desktop you use RDP. For Linux, SSH is the standard.\nHow SSH Works (Short Version) Client — on your laptop (where you type) Server — on the remote machine (what you connect to) You run ssh user@ip, enter your password (or use a key), and you get a shell on the remote box.\nInstall the SSH Client Check if you already have it:\nssh If you see usage info, you\u0026rsquo;re set. If not:\nUbuntu:\nsudo apt update sudo apt install openssh-client Arch Linux:\nsudo pacman -S openssh Install the SSH Server The machine you want to connect to needs the server package.\nOn that remote machine, test:\nssh localhost If it fails, install the server:\nUbuntu:\nsudo apt update sudo apt install openssh-server Arch Linux:\nsudo pacman -S openssh sudo systemctl enable --now sshd Check it\u0026rsquo;s running:\nsudo systemctl status sshd Ubuntu does not ship with the SSH server installed — you have to add it yourself.\nConnect From your local machine:\nssh your_username@host_ip_address First time you connect, it asks you to trust the server\u0026rsquo;s fingerprint. Type yes.\nReal Example From My Setup I sometimes SSH from my Arch machine to an Ubuntu VM:\nssh ubuntu@192.168.1.80 And the other way:\nssh ayysaad@192.168.1.20 Swap the username and IP for your own setup.\nQuick Reference Task Ubuntu Arch Linux Install client sudo apt install openssh-client sudo pacman -S openssh Install server sudo apt install openssh-server sudo pacman -S openssh Enable server starts automatically sudo systemctl enable --now sshd Connect ssh user@ip ssh user@ip ","permalink":"https://ayyzenn.github.io/posts/ssh/","summary":"\u003ch2 id=\"why-i-use-ssh\"\u003eWhy I Use SSH\u003c/h2\u003e\n\u003cp\u003eSSH is how I control another machine from my terminal. I use it to manage servers, copy files, and run commands on remote systems without sitting in front of them.\u003c/p\u003e\n\u003cp\u003eFor Windows remote desktop you use RDP. For Linux, SSH is the standard.\u003c/p\u003e\n\u003ch2 id=\"how-ssh-works-short-version\"\u003eHow SSH Works (Short Version)\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eClient\u003c/strong\u003e — on your laptop (where you type)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eServer\u003c/strong\u003e — on the remote machine (what you connect to)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou run \u003ccode\u003essh user@ip\u003c/code\u003e, enter your password (or use a key), and you get a shell on the remote box.\u003c/p\u003e","title":"SSH: How I Connect to Remote Servers"},{"content":"Why I Dual-Booted I needed Windows for some things and Linux for everything else. Instead of picking one, I installed Ubuntu alongside Windows on the same machine. This is the process I followed.\nSteps to Dual Boot Ubuntu with Windows Step 1: Prepare a Bootable USB Drive Download the latest Ubuntu ISO from the official Ubuntu website. Download and install Rufus or balenaEtcher to create a bootable USB. Insert a USB drive (at least 8GB) into your computer. Open Rufus or balenaEtcher: Select the Ubuntu ISO file. Choose the USB drive. Click \u0026ldquo;Start\u0026rdquo; to create the bootable drive. Step 2: Configure BIOS/UEFI Settings Restart your computer and enter BIOS/UEFI by pressing a specific key (usually F2, F12, Del, or Esc) during startup. Look for Boot Order/Boot Priority and set the USB drive as the first boot device. Disable Secure Boot (if enabled) under the Security tab. Enable UEFI mode (recommended) instead of Legacy mode. Save changes and exit BIOS. Step 3: Boot from the USB Drive Insert the bootable USB drive into your computer. Restart the system and press the Boot Menu key (F12, F9, Esc, or as per your motherboard). Select the bootable USB drive from the list. Press Enter to boot into Ubuntu Live. Step 4: Try or Install Ubuntu Once the Ubuntu installer loads, you will see two options: Try Ubuntu – Runs Ubuntu without installing it. Install Ubuntu – Proceeds with the installation. Click on Install Ubuntu to continue. Step 5: Select Keyboard Layout Choose your preferred keyboard layout and language. Click Continue. Step 6: Connect to Wi-Fi (Optional) Select your Wi-Fi network. Enter the password and click Continue. Step 7: Choose Installation Type Select Normal Installation (recommended). Check the box Install third-party software for graphics and Wi-Fi hardware, Flash, MP3, and other media. Click Continue. Step 8: Choose Installation Method Select Something else (for manual partitioning). Click Continue. Step 9: Create Partitions for Ubuntu Ubuntu requires three main partitions:\nBoot Loader Partition\nSelect Free Space and click Add. Assign 512MB to 1GB for the boot partition. Choose Ext4 as the filesystem type. Set Mount Point as /boot. Click OK. Swap Partition\nSelect Free Space and click Add. Allocate swap size (equal to or twice your RAM size). Choose Use as → Swap Area. Click OK. Root Partition\nSelect Free Space and click Add. Allocate remaining space for root (/). Choose Ext4 as the filesystem type. Set Mount Point as /. Click OK. Step 10: Begin Installation Verify all partitions. Click Install Now. A confirmation dialog will appear; click Continue. Step 11: Select Timezone Choose your region/time zone. Click Continue. Step 12: Create User Account Enter your name, username, and password. Click Continue. Step 13: Installation Process The installation will begin. Wait for the process to complete (it may take 10-30 minutes). Step 14: Restart the System Once the installation is completed, click Restart Now. Remove the bootable USB when prompted. Press Enter. Step 15: Choose Ubuntu at Boot When your system reboots, you will see a GRUB menu. Select Ubuntu to boot into Ubuntu. If you want to boot into Windows, choose Windows Boot Manager. Conclusion Congratulations! You have successfully dual-booted Ubuntu alongside Windows. You can switch between Windows and Ubuntu anytime by selecting the option in the boot menu.\n","permalink":"https://ayyzenn.github.io/posts/dual_boot/","summary":"\u003ch2 id=\"why-i-dual-booted\"\u003eWhy I Dual-Booted\u003c/h2\u003e\n\u003cp\u003eI needed Windows for some things and Linux for everything else. Instead of picking one, I installed \u003cstrong\u003eUbuntu alongside Windows\u003c/strong\u003e on the same machine. This is the process I followed.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps-to-dual-boot-ubuntu-with-windows\"\u003eSteps to Dual Boot Ubuntu with Windows\u003c/h2\u003e\n\u003ch3 id=\"step-1-prepare-a-bootable-usb-drive\"\u003e\u003cstrong\u003eStep 1: Prepare a Bootable USB Drive\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eDownload the latest Ubuntu ISO from the \u003ca href=\"https://ubuntu.com/download\"\u003eofficial Ubuntu website\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eDownload and install \u003ca href=\"https://rufus.ie\"\u003eRufus\u003c/a\u003e or \u003ca href=\"https://www.balena.io/etcher/\"\u003ebalenaEtcher\u003c/a\u003e to create a bootable USB.\u003c/li\u003e\n\u003cli\u003eInsert a USB drive (at least 8GB) into your computer.\u003c/li\u003e\n\u003cli\u003eOpen Rufus or balenaEtcher:\n\u003cul\u003e\n\u003cli\u003eSelect the Ubuntu ISO file.\u003c/li\u003e\n\u003cli\u003eChoose the USB drive.\u003c/li\u003e\n\u003cli\u003eClick \u0026ldquo;Start\u0026rdquo; to create the bootable drive.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-2-configure-biosuefi-settings\"\u003e\u003cstrong\u003eStep 2: Configure BIOS/UEFI Settings\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eRestart your computer and enter BIOS/UEFI by pressing a specific key (usually \u003cstrong\u003eF2, F12, Del, or Esc\u003c/strong\u003e) during startup.\u003c/li\u003e\n\u003cli\u003eLook for \u003cstrong\u003eBoot Order/Boot Priority\u003c/strong\u003e and set the USB drive as the first boot device.\u003c/li\u003e\n\u003cli\u003eDisable \u003cstrong\u003eSecure Boot\u003c/strong\u003e (if enabled) under the Security tab.\u003c/li\u003e\n\u003cli\u003eEnable \u003cstrong\u003eUEFI mode\u003c/strong\u003e (recommended) instead of Legacy mode.\u003c/li\u003e\n\u003cli\u003eSave changes and exit BIOS.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-3-boot-from-the-usb-drive\"\u003e\u003cstrong\u003eStep 3: Boot from the USB Drive\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eInsert the bootable USB drive into your computer.\u003c/li\u003e\n\u003cli\u003eRestart the system and press the \u003cstrong\u003eBoot Menu key\u003c/strong\u003e (F12, F9, Esc, or as per your motherboard).\u003c/li\u003e\n\u003cli\u003eSelect the bootable USB drive from the list.\u003c/li\u003e\n\u003cli\u003ePress \u003cstrong\u003eEnter\u003c/strong\u003e to boot into Ubuntu Live.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-4-try-or-install-ubuntu\"\u003e\u003cstrong\u003eStep 4: Try or Install Ubuntu\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eOnce the Ubuntu installer loads, you will see two options:\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eTry Ubuntu\u003c/strong\u003e – Runs Ubuntu without installing it.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInstall Ubuntu\u003c/strong\u003e – Proceeds with the installation.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eClick on \u003cstrong\u003eInstall Ubuntu\u003c/strong\u003e to continue.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-5-select-keyboard-layout\"\u003e\u003cstrong\u003eStep 5: Select Keyboard Layout\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eChoose your preferred \u003cstrong\u003ekeyboard layout\u003c/strong\u003e and language.\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eContinue\u003c/strong\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-6-connect-to-wi-fi-optional\"\u003e\u003cstrong\u003eStep 6: Connect to Wi-Fi (Optional)\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eSelect your Wi-Fi network.\u003c/li\u003e\n\u003cli\u003eEnter the password and click \u003cstrong\u003eContinue\u003c/strong\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-7-choose-installation-type\"\u003e\u003cstrong\u003eStep 7: Choose Installation Type\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eSelect \u003cstrong\u003eNormal Installation\u003c/strong\u003e (recommended).\u003c/li\u003e\n\u003cli\u003eCheck the box \u003cstrong\u003eInstall third-party software for graphics and Wi-Fi hardware, Flash, MP3, and other media\u003c/strong\u003e.\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eContinue\u003c/strong\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-8-choose-installation-method\"\u003e\u003cstrong\u003eStep 8: Choose Installation Method\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eSelect \u003cstrong\u003eSomething else\u003c/strong\u003e (for manual partitioning).\u003c/li\u003e\n\u003cli\u003eClick \u003cstrong\u003eContinue\u003c/strong\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"step-9-create-partitions-for-ubuntu\"\u003e\u003cstrong\u003eStep 9: Create Partitions for Ubuntu\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003eUbuntu requires three main partitions:\u003c/p\u003e","title":"Dual Boot Ubuntu and Windows: What I Did"},{"content":"Why I Picked Hugo I wanted a blog that loads fast, doesn\u0026rsquo;t need a database, and lets me write posts in Markdown. Hugo does exactly that — it builds plain HTML files from my content, and I host them on GitHub Pages.\nNo WordPress, no PHP, no MySQL. Just markdown files and a hugo command.\nWhat I Like About It Fast builds — my whole site compiles in under a second Markdown posts — write in any editor, commit to Git Themes — I\u0026rsquo;m using PaperMod GitHub Pages — free hosting, deploys on push How I Set Up Hugo 1. Install Hugo On Arch Linux sudo pacman -S hugo On Ubuntu sudo apt update sudo apt install hugo Or install the extended edition from the official release page (recommended for themes that use Sass):\nwget https://github.com/gohugoio/hugo/releases/download/v0.163.3/hugo_extended_0.163.3_linux-amd64.deb sudo dpkg -i hugo_extended_0.163.3_linux-amd64.deb On macOS (Using Homebrew) brew install hugo Alternatively, you can download the latest binary from Hugo\u0026rsquo;s official GitHub releases.\n2. Create a New Hugo Site hugo new site my-blog cd my-blog This will generate a new Hugo project with the default folder structure.\n3. Add a Theme Hugo supports themes, which you can find at Hugo Themes. To install a theme, clone it into the themes directory:\ngit init git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/hugo-PaperMod Then, edit hugo.toml and set the theme:\ntheme = \u0026#34;hugo-PaperMod\u0026#34; 4. Create Content Generate a new post using:\nhugo new posts/my-first-post.md Edit the Markdown file in content/posts/ and add your content.\n5. Start the Local Server Run the Hugo development server:\nhugo server --noHTTPCache Visit http://localhost:1313 to see your site in action.\n6. Build and Deploy To generate static files for deployment, run:\nhugo This will create a public/ folder containing your site\u0026rsquo;s HTML files.\nHosting on GitHub Pages To host your Hugo site on GitHub Pages, follow these steps:\n1. Create a GitHub Repository Go to GitHub and log in. Click the + icon in the top-right corner and select New repository. Enter a repository name: yourusername.github.io (replace yourusername with your actual GitHub username). Click Create repository. 2. Create a GitHub Actions Workflow for Deployment Create a workflow directory and a YAML configuration file in your main Hugo site directory:\nmkdir -p .github/workflows cd .github/workflows touch hugo.yaml 3. Add the GitHub Actions Workflow Configuration Edit hugo.yaml and add the following content:\nname: Deploy Hugo site to Pages on: push: branches: - master workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: false defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest env: HUGO_VERSION: 0.163.3 steps: - name: Install Hugo CLI run: | wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \\ \u0026amp;\u0026amp; sudo dpkg -i ${{ runner.temp }}/hugo.deb - name: Install Dart Sass run: sudo snap install dart-sass - name: Checkout Repository uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: Setup GitHub Pages id: pages uses: actions/configure-pages@v5 - name: Install Node.js Dependencies run: \u0026#34;[[ -f package-lock.json || -f npm-shrinkwrap.json ]] \u0026amp;\u0026amp; npm ci || true\u0026#34; - name: Build Hugo Site env: HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache HUGO_ENVIRONMENT: production run: | hugo --gc --minify --baseURL \u0026#34;${{ steps.pages.outputs.base_url }}/\u0026#34; - name: Upload Site Artifacts uses: actions/upload-pages-artifact@v3 with: path: ./public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 4. Add Files for Your Website Then add your Hugo-generated site files to the repository, commit, and push:\ngit add . git commit -m \u0026#34;Initial commit\u0026#34; git remote add origin https://github.com/YOUR_GITHUB_USERNAME/YOUR_GITHUB_REPO.git git push -u origin master 5. Configure the Repository for GitHub Pages Go to the Settings tab of your repository. Scroll down to the Pages section (Settings \u0026gt; Pages). Change the Source to GitHub Actions. 6. Check Deployment Status In your GitHub repository, navigate to Actions from the main menu. Look for the workflow named Deploy Hugo site to Pages. Once the deployment is complete, the status indicator should turn green. 7. Verify Your GitHub Pages Site Your site will be available at:\nhttps://yourusername.github.io/ (replace yourusername with your GitHub username).\nConclusion That\u0026rsquo;s how this site runs — Hugo locally, GitHub Pages in production. If you want a simple personal blog without managing a CMS, Hugo is worth trying.\n","permalink":"https://ayyzenn.github.io/posts/hugo/","summary":"\u003ch2 id=\"why-i-picked-hugo\"\u003eWhy I Picked Hugo\u003c/h2\u003e\n\u003cp\u003eI wanted a blog that loads fast, doesn\u0026rsquo;t need a database, and lets me write posts in Markdown. \u003cstrong\u003eHugo\u003c/strong\u003e does exactly that — it builds plain HTML files from my content, and I host them on GitHub Pages.\u003c/p\u003e\n\u003cp\u003eNo WordPress, no PHP, no MySQL. Just markdown files and a \u003ccode\u003ehugo\u003c/code\u003e command.\u003c/p\u003e\n\u003ch2 id=\"what-i-like-about-it\"\u003eWhat I Like About It\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eFast builds\u003c/strong\u003e — my whole site compiles in under a second\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMarkdown posts\u003c/strong\u003e — write in any editor, commit to Git\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eThemes\u003c/strong\u003e — I\u0026rsquo;m using PaperMod\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGitHub Pages\u003c/strong\u003e — free hosting, deploys on push\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"how-i-set-up-hugo\"\u003eHow I Set Up Hugo\u003c/h2\u003e\n\u003ch3 id=\"1-install-hugo\"\u003e1. Install Hugo\u003c/h3\u003e\n\u003ch4 id=\"on-arch-linux\"\u003eOn Arch Linux\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo pacman -S hugo\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"on-ubuntu\"\u003eOn Ubuntu\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install hugo\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOr install the extended edition from the official release page (recommended for themes that use Sass):\u003c/p\u003e","title":"Hugo: The Static Site Generator I Use for This Blog"},{"content":"Hello — this is my first blog post on Hugo. I set this site up to document what I\u0026rsquo;m learning as I go. More posts coming soon.\n","permalink":"https://ayyzenn.github.io/posts/first_post/","summary":"\u003cp\u003eHello — this is my first blog post on Hugo. I set this site up to document what I\u0026rsquo;m learning as I go. More posts coming soon.\u003c/p\u003e","title":"My First Post"},{"content":"Saad Ahmad Software engineer in training — DevOps, infrastructure automation, and applied AI.\nI go by Ayyzenn online. I\u0026rsquo;m a student at FAST NUCES, and I build systems that are meant to run — not demos that only work in a screenshot.\nMy daily environment is Arch Linux with i3: a terminal-first setup where I design, automate, break things, and document what actually works.\nWhat I Do I work across two areas that reinforce each other:\nDevOps \u0026amp; Infrastructure\nI automate servers, ship CI/CD pipelines, write Ansible playbooks, and deploy with containers. If a task repeats, it becomes code. If it has to run across machines, it becomes infrastructure.\nAI \u0026amp; Intelligent Systems\nI build RAG pipelines, multi-agent workflows, and systems that answer questions from documents or databases — often fully local, with no data leaving the machine.\nWhen those worlds meet, the work gets interesting: Jenkins pulling from GitHub, building images, and deploying automatically; agents that route questions, decompose them, and return grounded answers.\nSelected Work Each item below has a full write-up in the posts.\nMulti-Agent RAG — Router + generator agents with ChromaDB, HyDE, and query decomposition for simple lookups through multi-hop questions. Text-to-SQL — Natural-language questions over a database with Ollama, LangChain, and SQLAlchemy. Local RAG Knowledge Base — Offline retrieval with Ollama, ChromaDB, and sentence-transformers. YouTube Summarizer — LangGraph + Gemini agentic workflow for fetch, summarize, and Q\u0026amp;A. Jenkins + Docker CI/CD — GitHub → build image → run container, end to end. Ansible for Docker \u0026amp; Kubernetes — Playbooks that prepare Ubuntu servers as Kubernetes nodes. Scraping + REST API — FastAPI/Flask backend with BeautifulSoup, Selenium, PostgreSQL, and MongoDB. How I Work I learn by shipping. I build on real machines, with real configs, against real failure modes — then I write it down so the next pass is cleaner.\nIf it doesn’t run on my hardware, it doesn’t make the blog.\nBrowse by focus: DevOps, AI-ML, or Others.\nStack Linux \u0026amp; Systems Arch Linux Ubuntu Gentoo Debian Linux Bash Vim SSH Unix GCC DevOps \u0026amp; Cloud Docker Kubernetes Jenkins Ansible Terraform Azure AWS Git GitHub Grafana AI / ML \u0026amp; Data Python Jupyter Pandas NumPy Keras MongoDB PostgreSQL Selenium Kaggle Anaconda Web \u0026amp; Development FastAPI Flask Django HTML5 JavaScript Hugo VS Code Markdown YAML C Timeline When What 2020 Started this site and began writing in public. 2021 Dual-boot Ubuntu. SSH, Git, Azure — first real infrastructure work. 2022 Deep DevOps: Docker, Puppet, Chef, services, Gentoo from source. 2025 Applied AI: local LLMs, RAG, agentic workflows, Text-to-SQL. 2026 Combining both: Ansible, Jenkins CI/CD, APIs, multi-agent systems. Connect GitHub LinkedIn Email \u0026ldquo;They call us dreamers\u0026hellip; But we are the ones who don\u0026rsquo;t sleep.\u0026rdquo;\n","permalink":"https://ayyzenn.github.io/about/","summary":"about","title":"About"}]