<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[zabirauf || Zohaib]]></title><description><![CDATA[Blog about technology and things I learn]]></description><link>https://zohaib.me/</link><image><url>https://zohaib.me/favicon.png</url><title>zabirauf || Zohaib</title><link>https://zohaib.me/</link></image><generator>Ghost 5.88</generator><lastBuildDate>Sun, 05 Apr 2026 12:42:52 GMT</lastBuildDate><atom:link href="https://zohaib.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[How to safely connect cloud server to home GPU server]]></title><description><![CDATA[Unlock home GPU power for cloud workloads with Tailscale and Docker. I demo a simple architecture that tunnels requests to your local rig, skipping pricey cloud GPUs. All ports stay hidden, ensuring a tight, cost-effective environment.]]></description><link>https://zohaib.me/safely-connect-cloud-server-to-home-gpu-server/</link><guid isPermaLink="false">67822c0d181dcc1651a77fe2</guid><category><![CDATA[digital ocean]]></category><category><![CDATA[docker]]></category><category><![CDATA[how-to]]></category><category><![CDATA[homelab]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Thu, 13 Feb 2025 09:04:55 GMT</pubDate><media:content url="https://zohaib.me/content/images/2025/02/Screenshot-2025-02-13-at-01.06.14.png" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2025/02/Screenshot-2025-02-13-at-01.06.14.png" alt="How to safely connect cloud server to home GPU server"><p>I&apos;ve been building <a href="https://webtomarkdown.com/?ref=zohaib.me" rel="noreferrer"><strong>webtomarkdown.com</strong></a>&#xA0;to have a tool that allows me to convert various type of content to Markdown so I can pass it to LLMs <em>(Markdown is the language of LLMs).</em></p><p>I started with <a href="https://github.com/microsoft/markitdown?ref=zohaib.me">microsoft/markitdown</a> which is a really nice and fast library. But it does poorly on PDF that are beyond basic text e.g. tables, images etc. In my research and trying it out I see the <a href="https://github.com/DS4SD/docling?ref=zohaib.me">DS4SD/docling</a> does a lot better in extracting content from PDF. One of the reason it is able to do it is because they trained a neural network based model <a href="https://huggingface.co/ds4sd/docling-models?ref=zohaib.me">huggingface/ds4sd/docling-models</a> which takes an image and extract the content. </p><p>Running it requires GPU and Cloud-based GPUs, come with a hefty price tag. I don&apos;t want to pay for it, when the primary user of the tool is myself and maybe few who explore it. I wanted to tap into my home rig (packing a 4090 and a 3090) which I currently use for Local LLMs and want to utilize it for this.</p><p>The big question: <strong>How do I expose my home server to my cloud environment without throwing security out the window?</strong>&#xA0;</p><p>The answer:&#xA0;<strong>Docker</strong>&#xA0;and&#xA0;<strong>Tailscale</strong>. Below is how I set it up, keeping GPU usage cheaper and my machine safe from unwanted traffic.</p><h2 id="using-tailscale-for-secure-networking">Using Tailscale for Secure Networking</h2><p><a href="https://tailscale.com/?ref=zohaib.me" rel="nofollow">Tailscale</a> provides a simple, secure VPN solution built on WireGuard. It allows devices to connect directly and securely, forming a mesh network that&apos;s perfect for securely linking to machines. In this case two containers</p><ul><li>I have my actual service deployed in Digital Ocean Droplet with Docker Compose</li><li>I want to expose service from my home GPU server only for docling, wrapped in a different service.</li><li>The home GPU server shouldn&apos;t open any ports to public internet but rather my  container in Droplet should connect to container in my home server using a VPN (Virtual Private Network).</li></ul><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2025/01/Untitled-Diagram.drawio.svg" class="kg-image" alt="How to safely connect cloud server to home GPU server" loading="lazy" width="715" height="900"><figcaption><span style="white-space: pre-wrap;">Network topology</span></figcaption></figure><h1 id="setup-tailscale">Setup Tailscale</h1><p>Tailscale for various tasks, but isolation is essential to ensure each device only accesses what it should. Tailscale&#x2019;s<a href="https://tailscale.com/kb/1018/acls?ref=zohaib.me"> Access Controls (ACLs)</a> help you achieve this. By combining tags and ACLs, you can ensure that in this scenario, two containers only have access to each other&#x2014;and not the rest of your network&#x2014;effectively containing potential security issues within those containers. </p><p>ACLs function by letting you specify explicit rules for what can communicate with what, while anything not covered remains off-limits. </p><p>The following rules allow containers tagged as <code>your-svc-tag</code> to communicate only with each other, unless other rules permit additional connections</p><pre><code class="language-json">//Allow traffic from devices with tag:your-svc-tag devices with same tag
{
    &quot;action&quot;: &quot;accept&quot;,
    &quot;src&quot;:    [&quot;tag:your-svc-tag&quot;],
    &quot;dst&quot;:    [&quot;tag:your-svc-tag:*&quot;],
},</code></pre><h2 id="setup-docker-with-tailscale">Setup Docker with Tailscale</h2><p>The way your Docker container works with Tailscale is that you have container for Tailscale that is handling the networking and then your app container uses the network through the Tailscale container.</p><p>Following is the example of <code>docker-compose.yml</code> file that I use for my home machine which has the GPU that I want to expose to my Digital Ocean Droplet VM</p><pre><code class="language-YAML">version: &quot;3.8&quot;

services:

  app:
    container_name: your-service-name
    # ... (other properties)
    environment:
      # ... (other properties)
      - NVIDIA_VISIBLE_DEVICES=all
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              capabilities: [gpu]
              device_ids: [&apos;0&apos;]
    depends_on:
      # ... (other properties)
      - tailscale
    restart: unless-stopped

  tailscale:
    image: tailscale/tailscale:latest
    hostname: name-of-service-host
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY}
      - TS_HOSTNAME=name-of-service-host
      - TS_EXTRA_ARGS=--advertise-tags=tag:your-svc-tag
      - TS_SERVE_CONFIG=/etc/tailscale.config.json
    volumes:
      - /var/lib:/var/lib
      - /dev/net/tun:/dev/net/tun
      - ./config/tailscale.config.json:/etc/tailscale.config.json
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - SYS_MODULE
    restart: unless-stopped</code></pre><p>Here some of the key things are</p><ul><li>TS_AUTHKEY: This is auth key you generated for this purpose. We&apos;ll talk more on how to generate one.</li><li>TS_HOSTNAME: This is what you will use to connect to this node, also what shows up in Tailscale Dashboard</li><li>TS_EXTRA_ARGS: It indicates that when Tailscale daemon is started then it advertises the tag that you created for this purpose. This is important as that is what will allow ACLs to work properly.</li><li>TS_SERVE_CONFIG: This is the Tailscale config, which will allow you to serve selected ports so that the Droplet can access it. You will be connecting to this service using <strong>name-of-service-host:PORT</strong>.</li></ul><h2 id="tailscale-config">Tailscale config</h2><p>The <strong>tailscale.config.json</strong> will be dependent upon how your service is setup, but for basic 8080 hosted service you can use something like following</p><pre><code class="language-json">{
  &quot;TCP&quot;: {
    &quot;8080&quot;: {
      &quot;HTTP&quot;: true
    }
  },
  &quot;Web&quot;: {
    &quot;${TS_CERT_DOMAIN}:8080&quot;: {
      &quot;Handlers&quot;: {
        &quot;/&quot;: {
          &quot;Proxy&quot;: &quot;http://app:8080&quot;
        }
      }
    }
  },
  &quot;AllowFunnel&quot;: {
    &quot;${TS_CERT_DOMAIN}:8080&quot;: false
  }
}</code></pre><h2 id="generate-auth-key">Generate Auth Key</h2><p>You can go to <a href="https://login.tailscale.com/admin/settings/keys?ref=zohaib.me">Tailscale Settings</a> and then click on &quot;Generate Auth Key&quot;, to see the following dialog where you need to select various settings</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/02/image-2.png" class="kg-image" alt="How to safely connect cloud server to home GPU server" loading="lazy" width="1024" height="1888" srcset="https://zohaib.me/content/images/size/w600/2025/02/image-2.png 600w, https://zohaib.me/content/images/size/w1000/2025/02/image-2.png 1000w, https://zohaib.me/content/images/2025/02/image-2.png 1024w" sizes="(min-width: 720px) 720px"></figure><ul><li>Reusable: As your container can start again, I believe this is needed so auth key can be reused when you deploy an updated container</li><li>Ephemeral: If you restart/create new container and old one goes offline then it gets auto-removed</li><li>Pre-approved: This is up to you depending if you want these containers to automatically connect or need your explicit approval whenever you restart it.</li><li>Tags: Select the &apos;your-svc-tag&apos; here</li><li>Expiration: Set whatever you are comfortable with but be aware that if you start a new container after the expiry time, Tailscale will fail to connect and you would need to generate a new auth key.</li></ul><h2 id="host-discovery">Host discovery</h2><p>Although you&#x2019;ve set a hostname for your service, when containers restart, they connect to Tailscale with an incrementing suffix. For instance, if your hostname was foobar-hostname, it might register as foobar-hostname-1, and so on.</p><p>To identify the correct hostname to connect to, you need some form of host discovery. One approach is using Tailscale APIs to find an online node whose hostname begins with the given prefix.</p><pre><code class="language-Python">import requests
from datetime import datetime, timezone
from typing import Optional, List

class TailscaleDiscovery:
    def __init__(self, api_key: str):
        self.api_key = api_key

    def find_node(self, name_prefix: str, max_idle_seconds: int = 300) -&gt; Optional[str]:
        &quot;&quot;&quot;
        Find the most recently active Tailscale node matching the given prefix.
        
        Args:
            name_prefix: Prefix to match node names against
            max_idle_seconds: Maximum seconds since node was last seen (default: 300)
        
        Returns:
            str: Hostname of the matched node, or None if no match found
        &quot;&quot;&quot;
        try:
            # Fetch all nodes
            response = requests.get(
                &quot;https://api.tailscale.com/api/v2/tailnet/-/devices&quot;,
                headers={&quot;Authorization&quot;: f&quot;Bearer {self.api_key}&quot;}
            )
            response.raise_for_status()
            nodes = response.json()[&quot;devices&quot;]

            # Filter and sort matching nodes
            matching_nodes = []
            now = datetime.now(timezone.utc)
            
            for node in nodes:
                # Skip nodes that don&apos;t match prefix
                if not node[&quot;name&quot;].startswith(name_prefix):
                    continue
                    
                # Check if node is recently active
                last_seen = datetime.fromisoformat(node[&apos;lastSeen&apos;].replace(&apos;Z&apos;, &apos;+00:00&apos;))
                if (now - last_seen).total_seconds() &gt; max_idle_seconds:
                    continue
                    
                matching_nodes.append(node)

            if not matching_nodes:
                return None

            # Return hostname of most recently seen node
            return sorted(
                matching_nodes,
                key=lambda n: n[&apos;lastSeen&apos;],
                reverse=True
            )[0][&apos;hostname&apos;]

        except Exception:
            return None</code></pre><p>In this code, you retrieve the list of devices, filter those matching the provided prefix, and check when each device was last seen (i.e., the most recent time it was online). </p><p>You can adapt this further to cache domain information. If you have multiple machines, you could implement a round-robin approach to distribute requests. Once you identify the correct Tailscale hostname, you can simply call your service.</p><h2 id="some-issue-i-ran-into">Some issue I ran into</h2><p>when running Docker inside it. I couldn&#x2019;t get the Tailscale container on the Droplet to function properly&#x2014;particularly for DNS resolution. Instead, I ended up configuring Tailscale directly on the Droplet (with the Auth Key, Tags, etc.) and then set my container to use the host network:</p><pre><code>services:
  app:
    # ...(othe properties)
    network_mode: host</code></pre>]]></content:encoded></item><item><title><![CDATA[DeepSeek-R1: A Peek Under the Hood]]></title><description><![CDATA[DeepSeek-R1 uses cost-effective Reinforcement Learning to unlock emergent reasoning. By rewarding correct, verifiable steps, it refines logic and answers—showcasing how systematic feedback can reduce data needs and boost performance. Here I discuss my understanding from research paper.]]></description><link>https://zohaib.me/deepseek-r1-peek-under-the-hood/</link><guid isPermaLink="false">67a3d25b181dcc1651a7804b</guid><category><![CDATA[LLM-Training]]></category><category><![CDATA[LLM]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Sun, 09 Feb 2025 07:33:05 GMT</pubDate><media:content url="https://zohaib.me/content/images/2025/02/DeepSeek-R1-Pipeline-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2025/02/DeepSeek-R1-Pipeline-1.png" alt="DeepSeek-R1: A Peek Under the Hood"><p>DeepSeek-R1 has created quite a stir in the AI world, with headlines all over the place, primarily focusing on the low cost aspects of it to create a competitive model that butts head with o1 reasoning models from OpenAI.<br>I was more curious on the Reinforcement Learning aspects of it which I feel is step change for how these models are built. Some private labs may have already discovered this but its good to have these techniques now in the open and also proving their usefulness.</p><h2 id="current-challenges">Current challenges</h2><p>For Supervised Fine Tuning, you need a decent amount good quality data which either needs to be annotated by humans or through synthetic data generation where you would then need to filter between good vs bad generated data. This is a cumbersome process and also costly. For training reasoning, you would need data that would contain the reasoning step and there is not a whole lot of data on internet that does that.</p><p>One of the earlier emergent LLMs behavior was the Chain of Thought, which essentially was that when you ask LLM to &quot;think step by step&quot;, you would get reasoning steps and generally a better answer. DeepSeek-R1 uses this technique to train a model to make reasoning part of its response, out of the box.</p><h2 id="reinforcement-learning">Reinforcement learning</h2><p>The mechanism they used was Reinforcement Learning (RL), which refers to training the model through trial-and-error by rewarding outputs that contain correct, verifiable answers and reasoning steps. This iterative process helped the LLM systematically improve its reasoning capabilities as it learned from the reward signals.</p><p>There have been other approaches to train LLMs using reinforcement learning, but till now many of them were costly; either requiring more data, or more complex training, leading to costly training. What DeepSeek has done is make it cheaper and scalable to use RL for LLM training and avoiding Supervised Fine Tuning (SFT). </p><p>The main part of the training that help achieve this was <strong>Group Relative Policy Optimization</strong>. Its a reinforcement learning method which avoids a separate critic model (in previous research) by sampling multiple answers for a question and using group scores to estimate the baseline, which lowers the resources needed for training.</p><h2 id="deepseek-r1-zero">DeepSeek-R1-Zero</h2><p>This is where they used DeepSeek-V3-Base as the base model and applied reinforcement learning to enhance reasoning capabilities. While successful, they noticed the model&apos;s outputs had readability issues and occasionally mixed different languages.</p><h2 id="training-process">Training Process</h2><p>The reinforcement learning approach relied on multiple reward functions. Reward functions take the output and then give some +ve, 0, -ve reward.</p><ol><li><strong>Rule-based Rewards</strong>: They used questions with verifiable answers (code, math, logic) from their dataset. The model had to generate both reasoning steps and the final answer. Accurate answers received rewards while incorrect don&apos;t.</li><li><strong>Format Rewards</strong>: The model received additional rewards for following specific output formats, using <code>&lt;think&gt;...&lt;/answer&gt;</code> tags followed by <code>&lt;answer&gt;...&lt;/answer&gt;</code> tags.</li></ol><p>During each reinforcement cycle, the model:</p><ul><li>Generated multiple outputs for each question</li><li>Received rewards based on correctness and format</li><li>Used Group Reward Policy Optimization (GRPO) to incrementally improve towards better-rewarded outputs</li></ul><h2 id="emergent-behaviors">Emergent Behaviors</h2><p>What&apos;s fascinating is that they only provided a simple system prompt asking for reasoning in a specific format, without dictating how to reason. During training, the model developed self-reflection, self-verification capabilities, often re-evaluating and adjusting its reasoning mid-process. It also learned to reason for longer as the training went on.</p><p>This demonstrates the power of reinforcement learning - you don&apos;t explicitly teach how to reason, but rather let the model learn through rewards. This approach could potentially be applied to train models for various other capabilities, as long as you can create verifiable rewards. While this opens new possibilities for model development with less data dependency, some initial bootstrap data remains important (as we&apos;ll discuss later).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2025/02/image.png" class="kg-image" alt="DeepSeek-R1: A Peek Under the Hood" loading="lazy" width="1410" height="896" srcset="https://zohaib.me/content/images/size/w600/2025/02/image.png 600w, https://zohaib.me/content/images/size/w1000/2025/02/image.png 1000w, https://zohaib.me/content/images/2025/02/image.png 1410w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">DeepSeek-R1-Zero AIME accuracy during training</span></figcaption></figure><h2 id="deepseek-r1">DeepSeek R1</h2><p>Because of some of the issues with R1 e.g. readability, mixing of language, likely degradation on tasks that weren&apos;t specifically had verifiable rewards e.g. creative writing etc. they trained R1 to improve the overall usability of the model.</p><p>R1 was trained in multiple phases</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/02/DeepSeek-R1-Pipeline.png" class="kg-image" alt="DeepSeek-R1: A Peek Under the Hood" loading="lazy" width="822" height="622" srcset="https://zohaib.me/content/images/size/w600/2025/02/DeepSeek-R1-Pipeline.png 600w, https://zohaib.me/content/images/2025/02/DeepSeek-R1-Pipeline.png 822w" sizes="(min-width: 720px) 720px"></figure><h3 id="phase-1cold-start-with-sft">Phase 1 - Cold start with SFT</h3><p>Taking DeepSeek-V3-Base, used Supervised Fine Tuning on cold-start data that was collected from R1-Zero checkpoint. The data was likely handpicked to make sure it is of high quality.</p><h3 id="phase-2reasoning-oriented-rl">Phase 2 - Reasoning oriented RL</h3><p>Similar to R1-Zero, this phase used RL to train on reasoning related tasks on verifiable questions/answers. They did add another reward in the process which was <strong>Language Consistency Reward</strong>. This reward made sure the model avoided language mixing. Later they ran ablation experiments, where they remove this reward to understand its impact. What they saw was that Language Consistency Reward did decrease model performance but as it led to more human readable model, so its worth having.</p><h3 id="phase-3rejection-sampling-and-sft">Phase 3 - Rejection sampling and SFT</h3><p>After the Phase 2 is completed, from that model checkpoint, they collect more data for Supervised Fine Tuning for subsequent round. But here they not only have reasoning oriented data but also data for other domains e.g. creative writing, role-playing, general tasks to make sure the model is well rounded. Gathering good data is important.</p><p>For reasoning related data, they generate data containing accurate answers, doesn&apos;t have mixed-languages or long paragraph etc. At the end of process they have 600K reasoning related training samples.</p><p>For non-reasoning data, they use data they had for SFT in the DeepSeek-V3 model. The use DeepSeek-V3 to also generate reasoning data for such tasks, but also exclude it for tasks that don&apos;t require reasoning so that model also learns to avoid reasoning when its not needed.</p><p>Using all this data they train the DeepSeek-V3-Base with this curated dataset of 800K samples.</p><h3 id="phase-4rl-for-all-scenarios">Phase 4 - RL for all scenarios</h3><p>In this final state a round of Reinforcement Learning is done, but not only on reasoning data but also on other data where the reward is based on human preference. There is also safety related (harmlessness) evaluation done during this phase.</p><p>At the end of all these phases we get the DeepSeek-R1 model and my vibe check on that model has been pretty good.</p><h1 id="distillation">Distillation</h1><p>Lastly they use the R1 model to generate 800K sample data and use Supervised Fine Turning on various small language models e.g. Llama-3.3-70b-Instruct, Qwen2.5-32b etc. and observe that those models overall improve in complex tasks requiring reasoning.</p><p>The other interesting thing they also observe is that directly using RL on those models similar to R1-Zero, doesn&apos;t improve those models as much as distillation from larger model does.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2025/02/image-1.png" class="kg-image" alt="DeepSeek-R1: A Peek Under the Hood" loading="lazy" width="1878" height="466" srcset="https://zohaib.me/content/images/size/w600/2025/02/image-1.png 600w, https://zohaib.me/content/images/size/w1000/2025/02/image-1.png 1000w, https://zohaib.me/content/images/size/w1600/2025/02/image-1.png 1600w, https://zohaib.me/content/images/2025/02/image-1.png 1878w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Comparison of Distilled and RL models on reasoning-related benchmarks</span></figcaption></figure><h1 id="whats-likely-to-happen-next">Whats likely to happen next</h1><p>The year 2025 is going to be interesting, we are going to see much more research in direction of RL, many more distilled small models with reasoning capabilities that will be SOTA. The test time scaling will be something that Open source/weights models will also be pushing in direction off. Likely more (verifiable) domain specific small models may become easier to train due to RL. Anything that you can verify relatively cheaply in training process, you are likely to see much improvements in models related to those.</p><p>We are already seeing various OpenSource capability to train using RL (e.g. <a href="https://unsloth.ai/blog/r1-reasoning?ref=zohaib.me">Unsloth</a>). Here is my attempt to train reasoning with emojis in each step using Unsloth &#x1F604;</p><blockquote>&lt;reasoning&gt;<br>Her 200 sheep each produced 10 pounds of wool, so the total amount of wool is 200 * 10 = 2000 pounds &#x1F411;.<br>She sold one pound of wool for $20, so the total revenue is 2000 * $20 =$40,000 &#x1F4B8;.<br>The cost to her was $2000 &#x1F4B8;. <br>So, her profit is $40,000 - $2000 = $38,000 &#x1F911;.<br>&lt;/reasoning&gt;<br>&lt;answer&gt;<br>$38000 <br>&lt;/answer&gt;</blockquote><p>Its certainly going to be exciting 2025 for open source models.</p><hr><p>Disclaimer: I&apos;m no ML/AI expert, but a curious engineer who has been learning more about the inner workings of the LLMs to try to understand better the new techniques (when it comes to using them) and also how these models are becoming better. If you find something incorrect in my understanding please leave a comment.</p>]]></content:encoded></item><item><title><![CDATA[Using LLMs and Cursor to become a finisher]]></title><description><![CDATA[Struggling to finish side projects due to limited time? In my latest blog post, I share how I improved my productivity using AI tools like LLMs and Cursor IDE. Learn to refine specs, bootstrap code, and iterate effectively to rapidly build and deploy your projects—even with a busy schedule.]]></description><link>https://zohaib.me/using-llms-and-cursor-for-finishing-projects-productivity/</link><guid isPermaLink="false">6773a63a181dcc1651a77eaa</guid><category><![CDATA[LLM]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Thu, 02 Jan 2025 06:33:00 GMT</pubDate><media:content url="https://zohaib.me/content/images/2024/12/image-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2024/12/image-1.png" alt="Using LLMs and Cursor to become a finisher"><p>I transitioned to the role of Engineering Manager approximately 5 years ago, since then I haven&apos;t been programming in my day job but the itch to do so has always been there. So I continue to work on side projects to not lose touch and continue to hone my skills.</p><p>Because my time has always been limited, progress on side projects had been slow in the past, and many remained unfinished as life&apos;s events caused a loss of momentum, making them harder to resume. However, in the last year (2024), I have been very productive with my side projects, quickly building the tools or projects I need and deploying them for others to use&#x2014;in other words, finishing the v1 of each project.</p><p>A few examples of what I&apos;ve built are</p><ul><li><a href="https://jsonplayground.com/?ref=zohaib.me">jsonplayground.com</a> - JSON formatter but also in browser <a href="https://jqlang.github.io/jq/?ref=zohaib.me">JQ</a> using WASM so no data leaves the machine.</li></ul><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.25.07.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="1069" srcset="https://zohaib.me/content/images/size/w600/2024/12/Screenshot-2024-12-31-at-00.25.07.png 600w, https://zohaib.me/content/images/size/w1000/2024/12/Screenshot-2024-12-31-at-00.25.07.png 1000w, https://zohaib.me/content/images/size/w1600/2024/12/Screenshot-2024-12-31-at-00.25.07.png 1600w, https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.25.07.png 2000w" sizes="(min-width: 720px) 720px"></figure><ul><li><a href="http://webtomarkdown.com/?ref=zohaib.me">webtomarkdown.com</a> - As I often feel the need of converting files to Markdown, or parts of website to Markdown for passing in as context to LLMs. I&apos;m currently building this tool to solve that problem.</li></ul><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.24.29.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="1082" srcset="https://zohaib.me/content/images/size/w600/2024/12/Screenshot-2024-12-31-at-00.24.29.png 600w, https://zohaib.me/content/images/size/w1000/2024/12/Screenshot-2024-12-31-at-00.24.29.png 1000w, https://zohaib.me/content/images/size/w1600/2024/12/Screenshot-2024-12-31-at-00.24.29.png 1600w, https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.24.29.png 2000w" sizes="(min-width: 720px) 720px"></figure><ul><li>Face lift for my soaring club page <a href="https://www.dropbox.com/s/42oasltzi9n0k10/Screenshot%202024-12-31%20at%2000.22.40.png?dl=0&amp;ref=zohaib.me">Evergreen Soaring</a> where I volunteer (not deployed yet on official website).</li></ul><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.22.40.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="1569" srcset="https://zohaib.me/content/images/size/w600/2024/12/Screenshot-2024-12-31-at-00.22.40.png 600w, https://zohaib.me/content/images/size/w1000/2024/12/Screenshot-2024-12-31-at-00.22.40.png 1000w, https://zohaib.me/content/images/size/w1600/2024/12/Screenshot-2024-12-31-at-00.22.40.png 1600w, https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.22.40.png 2000w" sizes="(min-width: 720px) 720px"></figure><ul><li>A Chrome Browser Extension to automate parts of public messages we receive at my soaring club.</li><li><a href="https://fitinterval.com/?ref=zohaib.me">fitinterval.com</a> - Interval timer for workouts</li></ul><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.28.48.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="745" srcset="https://zohaib.me/content/images/size/w600/2024/12/Screenshot-2024-12-31-at-00.28.48.png 600w, https://zohaib.me/content/images/size/w1000/2024/12/Screenshot-2024-12-31-at-00.28.48.png 1000w, https://zohaib.me/content/images/size/w1600/2024/12/Screenshot-2024-12-31-at-00.28.48.png 1600w, https://zohaib.me/content/images/2024/12/Screenshot-2024-12-31-at-00.28.48.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>LLMs in general have been immense booster for my productivity when it comes to side projects and more specifically the <a href="https://www.cursor.com/?ref=zohaib.me">Cursor IDE</a> has been a great editor to use these LLMs for coding.</p><p>In this blog I&apos;ll go over what my high level flow looks like for greenfield projects and I hope that may help you. I do want to acknowledge that these tools are good in certain cases but may annoy you (waste time) in other areas, you just need to use them to figure out where specifically it&apos;s useful for you.</p><p>I have a nice habit tracker that I would like to replicate as a website, but all data stored locally, so let&apos;s use that as an example of what to create here.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/IMG_6790.jpg" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="3313" srcset="https://zohaib.me/content/images/size/w600/2025/01/IMG_6790.jpg 600w, https://zohaib.me/content/images/size/w1000/2025/01/IMG_6790.jpg 1000w, https://zohaib.me/content/images/size/w1600/2025/01/IMG_6790.jpg 1600w, https://zohaib.me/content/images/2025/01/IMG_6790.jpg 2000w" sizes="(min-width: 720px) 720px"></figure><p></p><h1 id="start-with-a-spec">Start with a spec</h1><p>I use the o1 ChatGPT to first get my application specification more refined. The reason I do that is so that it helps me scope the problem and also the spec I get at the end, I use it in further stages of bootstrapping the code. You can try to write the spec yourself, but I feel that going from few sentences to more detailed spec through ChatGPT o1 has been very useful in saving me time. I also ask it to further probe me with questions to further refine it.</p><p>Following the prompt I start with. </p><blockquote>I&#x2019;m want to build a website for habit tracking where user sees columns of months and each row being a date. They can simply select to indicate a day where they continued with the habit. It should store all that on local machine. Ask me more questions to refine the idea.</blockquote><p>It asks me bunch of questions which I answer, but then it continues asking me more questions. At some point, where you feel there is enough details, you should explicitly ask it to create a spec with the details that will allow another person/AI to build application. I also specify the technology I would prefer to use as thats what I&apos;m familiar with most.</p><blockquote>Answer those questions for me reasonably and create a spec that I can give to a person or another AI to help create the website. Make sure to have the details of project, user experience, technical details. I want to use typescript, react, tailwind css.</blockquote><p>You can read the whole chat here: <a href="https://chatgpt.com/share/6775ad7b-21e4-800e-a4c7-44c9bbbcc7a2?ref=zohaib.me" rel="noreferrer">Habit Tracking Website Plan</a></p><p>Now store that spec in <code>SPEC.md</code> in a folder where your project will be. We will continue to refer back to it when needed.</p><h1 id="bootstrap-project">Bootstrap project</h1><p>I use <a href="https://vite.dev/guide/?ref=zohaib.me">Vite</a> to bootstrap my project. This allows me to setup all the necessary tooling in a consistent manner. <br>In the directory of project I run <code>npm create vite@latest .</code> which will ask me question about which UX framework and Language to use. Once I have the project and <code>SPEC.md</code> in that project I use the Cursor Agent to create the initial code.</p><p>You can go to Composer &gt; Select Agent &gt; Added SPEC.md in the context and ask it to implement it.</p><p>This will go over your code, setup tailwind, update few files to create the initial version.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/Screenshot-2025-01-01-at-13.17.48.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="1552" height="1972" srcset="https://zohaib.me/content/images/size/w600/2025/01/Screenshot-2025-01-01-at-13.17.48.png 600w, https://zohaib.me/content/images/size/w1000/2025/01/Screenshot-2025-01-01-at-13.17.48.png 1000w, https://zohaib.me/content/images/2025/01/Screenshot-2025-01-01-at-13.17.48.png 1552w" sizes="(min-width: 720px) 720px"></figure><p>This is what the initial version looked like. Not exactly what I was looking for (skeuomorphic design) but close enough in structure that I can iterate over it.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/Screenshot-2025-01-01-at-13.36.34.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="1190" srcset="https://zohaib.me/content/images/size/w600/2025/01/Screenshot-2025-01-01-at-13.36.34.png 600w, https://zohaib.me/content/images/size/w1000/2025/01/Screenshot-2025-01-01-at-13.36.34.png 1000w, https://zohaib.me/content/images/size/w1600/2025/01/Screenshot-2025-01-01-at-13.36.34.png 1600w, https://zohaib.me/content/images/size/w2400/2025/01/Screenshot-2025-01-01-at-13.36.34.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>There is also some bug in it, where clicking on the button doesn&apos;t change the state. But overall, this puts us into a good starting point, it created the overall UX layout I expected, stored data in local storage, has the right export feature for Markdown. All of it in just order of minutes instead of hours.</p><p>P.S: Sometime I also use <a href="https://v0.dev/?ref=zohaib.me">v0.dev</a> to bootstrap the UX aspect of the project. That tools allows quicker iteration on the UX aspects.</p><h1 id="small-iterations">Small iterations</h1><p>You don&apos;t want to one-shot everything i.e. ask it to do multiple complicated tasks in one go. That can sometime work but can lead to issues and makes it harder (and slower as it will regenerate bunch of code that it doesn&apos;t need to change) to iterate. Follow a divide and conquer approach, i.e. split your feature into smaller tasks and iterate over them using the Chat/Composer.</p><p>Now first let&apos;s fix the bug and also change the UX. In my spec conversation with o1, I ask it to create a spec for UX focused more on skeuomorphic aspects of it. Then I use the Cursor Composer to update the code. I select the o1 model in this case.</p><blockquote>Update @App.tsx @MonthColumn.tsx @MonthColumn.css @App.css  to improve the UX, also fix the issue where the state isn&apos;t being changed when I click the button.<br><br>{PASTE THE UX SPEC}</blockquote><p>Here is what it looks like now. So it fixed the bug, and also updated the UX to have some more Led like behavior with depth, some shadows etc. It still look horrible but we will further iterate on that.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/image-1.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="1304" srcset="https://zohaib.me/content/images/size/w600/2025/01/image-1.png 600w, https://zohaib.me/content/images/size/w1000/2025/01/image-1.png 1000w, https://zohaib.me/content/images/size/w1600/2025/01/image-1.png 1600w, https://zohaib.me/content/images/size/w2400/2025/01/image-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>In the next iteration I gave the above screenshot (yes cursor can also use images for context) in Chat mode and first asked it to describe the details the button and then asked it to make necessary changes to replicate that. After couple of more iterations.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/image-2.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="1206" height="1130" srcset="https://zohaib.me/content/images/size/w600/2025/01/image-2.png 600w, https://zohaib.me/content/images/size/w1000/2025/01/image-2.png 1000w, https://zohaib.me/content/images/2025/01/image-2.png 1206w" sizes="(min-width: 720px) 720px"></figure><p>After few back and forth, I have the experience which looks good enough for the demo here.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/image-4.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="2000" height="1403" srcset="https://zohaib.me/content/images/size/w600/2025/01/image-4.png 600w, https://zohaib.me/content/images/size/w1000/2025/01/image-4.png 1000w, https://zohaib.me/content/images/size/w1600/2025/01/image-4.png 1600w, https://zohaib.me/content/images/size/w2400/2025/01/image-4.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now finally I need to setup deployment using GitHub actions, so whenever I check-in to main, it builds and deploys to GitHub pages. I already had a workflow in another of my repository that I wanted it to use as context and make specific changes to build this project. The good thing about Cursor is that you can also provide context by adding a link, so either its some existing code, some documentation, it can be passed to LLM for context. In my experience providing relevant context generally allows it to output better code and avoid hallucinations.<br></p><blockquote>Similar to @https://raw.githubusercontent.com/zabirauf/evergreensoaring-modern-web/refs/heads/main/.github/workflows/deploy.yml create a deployment to github pages and also make sure to npm install and npm run build (which puts it in dist folder). The dist is what needs to be deployed</blockquote><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2025/01/image-6.png" class="kg-image" alt="Using LLMs and Cursor to become a finisher" loading="lazy" width="1174" height="1044" srcset="https://zohaib.me/content/images/size/w600/2025/01/image-6.png 600w, https://zohaib.me/content/images/size/w1000/2025/01/image-6.png 1000w, https://zohaib.me/content/images/2025/01/image-6.png 1174w" sizes="(min-width: 720px) 720px"></figure><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">This all got deployed and you can try it out here: <br><br><a href="https://zabirauf.github.io/habit-tracker-example/?ref=zohaib.me">https://zabirauf.github.io/habit-tracker-example/</a></div></div><h1 id="overall-tips">Overall tips</h1><ol><li>Use LLM to hash out the details of the project and store it for further context</li><li>Use a tool or open-source template to bootstrap your project to setup all the necessary toolings and following a manageable project pattern.</li><li>Leverage Cursor Composer (agent mode) to bootstrap the project</li><li>Use mix of o1 and claude-3.5-sonnet. Generally I use o1 where broad stokes are needed e.g. 1st draft of a feature and then use Claude-3.5-Sonnet to further iterate on it. But I&apos;m using Claude-3.5-sonnet approx. 80% of times.</li><li>Select the right mode e.g. Chat, Composer (normal), Composer (agent). <br>I use Chat, when I need back and forth and know exactly where changes will be and want to see the changes before applying. <br>I use Composer (normal) when I need multi-file changes e.g. new feature. <br>I don&apos;t use Composer (agent) often enough yet. Composer (agent) can run commands in terminal, lint code, re-iterate etc, but going back to the principle of small iterations, I try to scope things to what I can review easily and add.</li><li>Provide relevant context as much as possible e.g. specific files you want changed, specific docs (links), or submit with codebase option in chat when you want it to search for relevant context.</li><li>Store markdown files relevant to your project so you can add those as context e.g. SPEC.md, documentation from website that you often get back from (plugging <a href="https://webtomarkdown.com/?ref=zohaib.me">https://webtomarkdown.com</a> for converting a website documentation to Markdown and storing it &#x1F604;)</li><li>Create and use .cursorrules file in your project directory for instructions that you want it to take in prompts, e.g. if you see it always using some library you don&apos;t want then add it to .cursorrules, specific technology that you want it to user in code e.g. Tailwind, certain component library e.g. Shadcn etc. This allows you to start nudging it in direction you want for most of your prompts.</li><li>Always make sure that you understand the code at high level so you don&apos;t land in a space where eventually it&apos;s such a messy code that it becomes hard for you to debug when LLMs can&apos;t find issues for you. My tip is to continue to split stuff into manageable pieces (hint, you can use LLMs to do it from time to time).</li></ol><h1 id="closing-remarks">Closing remarks</h1><p>I hope this has been helpful, and that you can start finishing the first versions of your projects and deploying them. By turning unfinished projects into completed and deployed ones, you can continue to build momentum even when you take small breaks. This approach allows you to gradually add more to your projects while keeping them manageable. I believe this also helps keep me motivated, as I get to see progress more quickly on what I want to deliver.</p>]]></content:encoded></item><item><title><![CDATA[Managing Secrets in NixOS Home Manager with SOPS]]></title><description><![CDATA[Discover how to securely manage secrets in NixOS Home Manager using SOPS and sops-nix. Learn to set up Age encryption, create encrypted secret files, integrate SOPS with NixOS, and access secrets as environment variables. Perfect for maintaining secure, declarative NixOS configurations.]]></description><link>https://zohaib.me/managing-secrets-in-nixos-home-manager-with-sops/</link><guid isPermaLink="false">671e9966181dcc1651a77e3e</guid><category><![CDATA[nixos]]></category><category><![CDATA[how-to]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Sun, 27 Oct 2024 20:30:28 GMT</pubDate><media:content url="https://zohaib.me/content/images/2024/10/2024-10-27-132308_hyprshot.png" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2024/10/2024-10-27-132308_hyprshot.png" alt="Managing Secrets in NixOS Home Manager with SOPS"><p>When setting up a NixOS system, we often want to store secrets in environment variables for various tools to use. For example, you might want to use the <a href="https://github.com/simonw/llm?ref=zohaib.me" rel="noreferrer">llm</a> CLI tool, which requires setting the <code>OPENAI_API_KEY</code> environment variable.</p><p>However, if you&apos;re using declarative configuration files or flakes to set up your NixOS system (which is a great practice!), you can&apos;t simply update those files with the secret. That would be insecure, especially if you&apos;re tracking your configurations in Git and pushing to a public repository.</p><h2 id="enter-sops">Enter SOPS</h2><p>To manage secrets on machines, we can use a tool called SOPS (Secrets OPerationS). SOPS allows you to manage secrets by encrypting them when storing them in a file. You use the tool to edit the secrets, and when you save, it writes them in an encrypted manner.</p><p>In NixOS, we have <a href="https://github.com/Mic92/sops-nix?ref=zohaib.me" rel="noreferrer">sops-nix</a>, which helps manage SOPS-based secrets using configuration. Here&apos;s how it works: when SOPS secrets are loaded from a file at runtime, each secret is stored in a different file. This means at runtime, you can read from those files to get the secret and export it as an environment variable.</p><h2 id="setting-up-sops-with-age">Setting Up SOPS with Age</h2><p>We&apos;ll use <a href="https://github.com/FiloSottile/age?ref=zohaib.me" rel="noreferrer">Age</a> as the encryption backend for SOPS. </p><h3 id="install-age-and-sops">Install Age and SOPS</h3><p>In your home manager where you manage packages make sure to add <code>age</code> and <code>sops</code> and rebuilt to install them.</p><h3 id="generate-an-age-key">Generate an Age Key</h3><p>First, create a directory for SOPS configuration and generate an Age key pair:</p><pre><code class="language-bash">mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
</code></pre><p>The public key is derived from the private key:</p><pre><code class="language-bash">age-keygen -y ~/.config/sops/age/keys.txt
</code></pre><p>Copy the output public key; you&apos;ll need it to encrypt your secrets.</p><h3 id="create-the-sopsyaml-configuration-file">Create the <code>.sops.yaml</code> Configuration File</h3><p>Create a <code>.sops.yaml</code> file in your project&apos;s root directory to define encryption rules and specify the public key:</p><pre><code class="language-yaml">   keys:
  - &amp;host_hostname &lt;YOUR PUBLIC KEY&gt;
creation_rules:
  - path_regex: secrets.yaml$
    key_groups:
    - age:
      - *host_hostname
</code></pre><p>Replace <code>&lt;YOUR_PUBLIC_KEY&gt;</code> with the public key you obtained earlier. And replace <code>hostname</code> with your machine host-name. This configuration tells SOPS to use Age for encrypting any file named <code>secrets.yaml</code> using the specified public key.</p><h3 id="create-and-encrypt-the-secrets-file">Create and Encrypt the Secrets File</h3><p>Use <code>nix-shell</code> with SOPS available to run creating the <code>secrets.yaml</code> file. Make sure to run it from the same directory where <code>.sops.yaml</code> exist. It will open the editor that is configured using <code>EDITOR</code> in your shell.</p><pre><code class="language-bash">nix-shell -p sops --run &quot;sops secrets.yaml&quot;</code></pre><p>Add your secrets to the file:</p><pre><code class="language-yaml">openai_api_key: &apos;YOUR_OPENAI_API_KEY&apos;
</code></pre><p>Save and exit the editor. SOPS will encrypt the file upon saving, and <code>secrets.yaml</code> will now contain encrypted data along with other metadata.</p><h2 id="integrate-sops-with-nixos-using-sops-nix">Integrate SOPS with NixOS Using sops-nix</h2><h3 id="update-your-flakenix">Update Your <code>flake.nix</code></h3><p>Add <code>sops-nix</code> to your Nix flakes:</p><pre><code class="language-nix">{
  inputs = {
    nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-23.05&quot;;
    sops-nix.url = &quot;github:Mic92/sops-nix&quot;;
    # other inputs...
  };

  outputs = { self, nixpkgs, sops-nix, ... }:
    let
      system = &quot;x86_64-linux&quot;;
      pkgs = import nixpkgs { inherit system; };
    in
    {
      nixosConfigurations.mySystem = pkgs.lib.nixosSystem {
        inherit system;
        modules = [
          ./configuration.nix
          sops-nix.nixosModules.sops
          # other modules...
          home-manager.nixosModules.home-manager
          {
            # other config...
            home-manager.sharedModules = [
              sops-nix.homeManagerModules.sops
            ];
          }
        ];
      };
    };
}
</code></pre><p><strong>Adding <code>sops-nix</code> to the Inputs</strong>:</p><pre><code class="language-nix">inputs = {
  nixpkgs.url = &quot;github:NixOS/nixpkgs/nixos-23.05&quot;;
  sops-nix.url = &quot;github:Mic92/sops-nix&quot;;
  # other inputs...
};
</code></pre><p>This line adds the <code>sops-nix</code> input, fetching the <code>sops-nix</code> repository from GitHub. It provides the necessary modules to integrate SOPS (Secrets OPerationS) into your NixOS system.</p><p><strong>Including the SOPS NixOS Module</strong>:</p><pre><code class="language-nix">modules = [
  ./configuration.nix
  sops-nix.nixosModules.sops
  # other modules...
];
</code></pre><p>By adding <code>sops-nix.nixosModules.sops</code> to the <code>modules</code> list, you&apos;re integrating the SOPS module into your NixOS system configuration. This enables system-wide management of encrypted secrets.</p><p><strong>Integrating SOPS with Home Manager</strong>:</p><pre><code class="language-nix">home-manager.nixosModules.home-manager
{
  # other config...
  home-manager.sharedModules = [
    sops-nix.homeManagerModules.sops
  ];
}
</code></pre><p>This section includes the SOPS module in Home Manager&apos;s shared modules by adding <code>sops-nix.homeManagerModules.sops</code>. It allows you to manage user-specific secrets through SOPS within your home environment configurations.</p><h3 id="configure-sops-in-homenix">Configure SOPS in <code>home.nix</code></h3><p>In your NixOS configuration, set up SOPS:</p><pre><code class="language-nix">{
  sops = {
    age.keyFile = &quot;/home/&lt;your username&gt;/.config/sops/age/keys.txt&quot;; # must have no password!

    defaultSopsFile = ./secrets.yaml;
    defaultSymlinkPath = &quot;/run/user/1000/secrets&quot;;
    defaultSecretsMountPoint = &quot;/run/user/1000/secrets.d&quot;;

    secrets.openai_api_key = {
      # sopsFile = ./secrets.yml.enc; # optionally define per-secret files
      path = &quot;${config.sops.defaultSymlinkPath}/openai_api_key&quot;;
    };
  };
}
</code></pre><p>This configuration tells NixOS to use the encrypted <code>secrets.yaml</code> file and makes the <code>openai_api_key</code> available at the specified path. Whenever you add a new secret, you should update the config here with that secret info as well.</p><h2 id="accessing-the-secret-as-an-environment-variable">Accessing the Secret as an Environment Variable</h2><p>In your config you can use <code>$(cat ${config.sops.secrets.openai_api_key.path})</code> to get the secret. For example I configure my zsh file to export the OPENAI_API_KEY.</p><pre><code class="language-bash"> programs.zsh = {
    initExtra = &apos;&apos;
      # other config...
      export OPENAI_API_KEY=$(cat ${config.sops.secrets.openai_api_key.path})
    &apos;&apos;;
  };</code></pre><h2 id="reload-configuration">Reload Configuration</h2><p>After updating your configurations, rebuild your NixOS system:</p><pre><code class="language-bash">sudo nixos-rebuild switch
</code></pre><p>Now, your <code>OPENAI_API_KEY</code> is securely stored and accessible as an environment variable without exposing the key in your configuration files.</p><h2 id="conclusion">Conclusion</h2><p>By integrating SOPS with NixOS, you maintain a declarative and secure configuration while managing sensitive secrets. This setup ensures that API keys and other confidential data remain encrypted in your repositories and are only decrypted on your local machine during runtime.</p><p>Remember to always be cautious when dealing with secrets and never commit unencrypted secrets to your repository!</p><h1 id="references">References</h1><ul><li><a href="https://0xda.de/blog/2024/07/framework-and-nixos-sops-nix-secrets-management/?ref=zohaib.me">Framework and NixOS - Sops-nix Secrets Management</a></li><li><a href="https://github.com/Mic92/sops-nix?ref=zohaib.me" rel="noreferrer">GitHub Mic92/sops-nix</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Run custom GGUF model on Ollama]]></title><description><![CDATA[Learn how to install a custom Hugging Face GGUF model using Ollama, enabling you to try out the latest LLM models locally. This guide covers downloading the model, creating a Modelfile, and setting up the model in Ollama and Open-WebUI.]]></description><link>https://zohaib.me/run-custom-gguf-model-on-ollama/</link><guid isPermaLink="false">66dbdf26181dcc1651a77df5</guid><category><![CDATA[ollama]]></category><category><![CDATA[how-to]]></category><category><![CDATA[LLM]]></category><category><![CDATA[open-webui]]></category><category><![CDATA[homelab]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Sat, 07 Sep 2024 05:47:32 GMT</pubDate><media:content url="https://zohaib.me/content/images/2024/09/Screenshot-2024-09-06-at-22.46.23.png" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2024/09/Screenshot-2024-09-06-at-22.46.23.png" alt="Run custom GGUF model on Ollama"><p>Ollama is a powerful and user friendly tool for running and managing large language models (LLMs) locally. But not all latest models maybe available on Ollama registry to pull and use. The fastest way maybe to directly download the GGUF model from Hugging Face. In this article, we&apos;ll explore how to install a custom Hugging Face GGUF model using Ollama, enabling you to try out latest models as soon as they are available.</p><p>I use Open-WebUI as the interface for Ollama, and all these instructions would allow you to use the model from Open-WebUI as well.</p><h3 id="1-download-the-model">1. Download the Model</h3><p>First, we need to acquire the GGUF model from Hugging Face. We&apos;ll use the <a href="https://huggingface.co/docs/huggingface_hub/en/guides/cli?ref=zohaib.me">Hugging Face CLI</a> for this:</p><pre><code class="language-bash">huggingface-cli download --help bartowski/Reflection-Llama-3.1-70B-GGUF Reflection-Llama-3.1-70B-Q4_K_S.gguf
</code></pre><p>This command downloads the specified GGUF model, which in this case is a fine-tuned version of LLaMa 3.1.</p><h3 id="2-create-a-modelfile">2. Create a Modelfile</h3><p><a href="https://github.com/ollama/ollama/blob/main/docs/modelfile.md?ref=zohaib.me">Modelfile</a> is the blueprint that Ollama uses to create and run models. Since we&apos;re working with a LLaMa 3.1 variant, we can base our Modelfile on an existing one:</p><pre><code class="language-bash">ollama show --modelfile llama3.1:70b-instruct-q4_0 &gt;&gt;  Modelfile
</code></pre><p>This command generates a Modelfile based on the llama3.1 model specifications which I already had locally pulled.</p><p>If you don&apos;t have example of existing Modelfile to reuse then you would need to figure it out from the Hugging-Face page for the model and then create one. </p><h3 id="3-update-the-modelfile">3. Update the Modelfile</h3><p>Now, we need to modify the Modelfile to point to our downloaded GGUF model. Open the Modelfile in a text editor and update the <code>FROM</code> line with the path to the downloaded model. The Hugging Face CLI will have printed this path at the end of the download process.</p><h3 id="4-create-the-model-in-ollama">4. Create the Model in Ollama</h3><p>Finally, we&apos;ll use Ollama to create our custom model:</p><pre><code class="language-bash">ollama create mattshumer/Reflection-Llama-3.1-70B:Q4_K_S -f Modelfile
</code></pre><p>This command processes the Modelfile and copies the model to Ollama&apos;s storage, typically located at <code>/usr/share/ollama/.ollama</code>. Here <code>mattshumer/Reflection-Llama-3.1-70B:Q4_K_S</code> is the name of the model that I will use in Ollama, you can name it whatever you want.</p><p>If you want to delete the hugging face cached model so you don&apos;t use double the storage then you can run the following which will start a Terminal UI for you to select which models to delete.</p><pre><code>huggingface-cli delete-cache</code></pre><p></p><p>By following these steps, you&apos;ve successfully installed a custom Hugging Face GGUF model using Ollama and in Open-WebUI. </p>]]></content:encoded></item><item><title><![CDATA[Guide to extending OpenWebUI using Pipelines]]></title><description><![CDATA[Explore OpenWebUI's Pipelines: extend your self-hosted LLM interface. Learn to create custom pipelines, from filters to tools. Intercept LLM interactions, implement function-calling, and integrate new providers. Enhance AI workflows or build RAG systems with this guide to OpenWebUI's extensibility.]]></description><link>https://zohaib.me/extending-openwebui-using-pipelines/</link><guid isPermaLink="false">66a922be7425b441e0bdc824</guid><category><![CDATA[LLM]]></category><category><![CDATA[development]]></category><category><![CDATA[open-webui]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Thu, 01 Aug 2024 05:23:08 GMT</pubDate><content:encoded><![CDATA[<p>OpenWebUI is a self-hosted UI for interacting with various LLM models, both on-device and in the cloud. I use it as my primary method of interacting with LLMs due to its wide variety of models, ability to keep data local with locally deployed models, and extensive features. I&apos;ve set it up along with Tailscale on my homelab so my family can access it with their own logins, maintaining their conversation history and settings.</p><p>Recently, I stumbled upon <a href="https://github.com/stanford-oval/storm?ref=zohaib.me">Storm</a> from Stanford, a tool that uses LLMs and Search to generate long Wiki-like articles. It&apos;s useful for my personal workflows by providing a jumping point for deeper research. I aimed to bring this functionality to OpenWebUI, so I began exploring Pipelines.</p><p>Pipelines serve as the mechanism in OpenWebUI to extend its capabilities. Unfortunately, they are still not well-documented, which led me to dig into the code, scratch my head, and debug to integrate Storm finally. I hope this post serves as an introductory guide for anyone trying to do the same, saving you a few hours of head-scratching.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2024/07/Untitled-2024-07-24-2230-4.png" class="kg-image" alt loading="lazy" width="1653" height="919" srcset="https://zohaib.me/content/images/size/w600/2024/07/Untitled-2024-07-24-2230-4.png 600w, https://zohaib.me/content/images/size/w1000/2024/07/Untitled-2024-07-24-2230-4.png 1000w, https://zohaib.me/content/images/size/w1600/2024/07/Untitled-2024-07-24-2230-4.png 1600w, https://zohaib.me/content/images/2024/07/Untitled-2024-07-24-2230-4.png 1653w" sizes="(min-width: 720px) 720px"></figure><h1 id="valves">Valves</h1><p>Briefly Valves is the mechanism for configuring your pipeline. This is the mechanism for user to change something about your pipeline e.g. if you want to get some API_KEY then that will be a Valve, if you want some value from user which will change your pipeline behavior then that will also be a Valve. The admin can see and update all these values from the OpenWebUI settings.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2024/07/image.png" class="kg-image" alt loading="lazy" width="2000" height="1028" srcset="https://zohaib.me/content/images/size/w600/2024/07/image.png 600w, https://zohaib.me/content/images/size/w1000/2024/07/image.png 1000w, https://zohaib.me/content/images/size/w1600/2024/07/image.png 1600w, https://zohaib.me/content/images/2024/07/image.png 2000w" sizes="(min-width: 720px) 720px"></figure><h1 id="pipelines">Pipelines</h1><p>Following are different type of pipelines you can create</p><h2 id="filter">Filter</h2><p>Filter pipelines allows you to intercept the user request/message before it goes to LLM model and also after the response comes from LLM model but before its sent to users. This is what can allow various scenarios such as</p><ul><li><strong>RAG</strong> to fetch more context and put it into the message to LLM to use.</li><li><strong>Tools</strong> that gets executed and adds any context for LLM</li><li><strong>Prompt injection filter</strong> to catch them before LLM gets to respond</li><li><strong>Safety filters</strong> e.g. using Meta LLamaGuard before user request is answered</li></ul><p>How I like to think of this is that if I want to do something before or after LLM is called then I would create a Filter pipeline.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2024/07/Untitled-2024-07-24-2230-5.png" class="kg-image" alt loading="lazy" width="2000" height="335" srcset="https://zohaib.me/content/images/size/w600/2024/07/Untitled-2024-07-24-2230-5.png 600w, https://zohaib.me/content/images/size/w1000/2024/07/Untitled-2024-07-24-2230-5.png 1000w, https://zohaib.me/content/images/size/w1600/2024/07/Untitled-2024-07-24-2230-5.png 1600w, https://zohaib.me/content/images/size/w2400/2024/07/Untitled-2024-07-24-2230-5.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Filter pipeline flow which is &quot;Chat request -&gt; Inlet -&gt; LLM model -&gt; Outlet -&gt; Chat response&quot;</span></figcaption></figure><p>Here is what a filter pipeline would look like</p><pre><code class="language-Python">from typing import List, Optional
from pydantic import BaseModel
from schemas import OpenAIChatMessage


class Pipeline:
    class Valves(BaseModel):
        # List target pipeline ids (models) that this filter will be connected to.
        # If you want to connect this filter to all pipelines, you can set pipelines to [&quot;*&quot;]
        pipelines: List[str] = []

        # Assign a priority level to the filter pipeline.
        # The priority level determines the order in which the filter pipelines are executed.
        # The lower the number, the higher the priority.
        priority: int = 0

        # Add your custom parameters/configuration here e.g. API_KEY that you want user to configure etc.
        pass

    def __init__(self):
        self.type = &quot;filter&quot;
        self.name = &quot;Filter&quot;
        self.valves = self.Valves(**{&quot;pipelines&quot;: [&quot;*&quot;]})

        pass

    async def on_startup(self):
        # This function is called when the server is started.
        print(f&quot;on_startup:{__name__}&quot;)
        pass

    async def on_shutdown(self):
        # This function is called when the server is stopped.
        print(f&quot;on_shutdown:{__name__}&quot;)
        pass

    async def inlet(self, body: dict, user: Optional[dict] = None) -&gt; dict:
        # This filter is applied to the form data BEFORE it is sent to the LLM API.
        print(f&quot;inlet:{__name__}&quot;)

        return body
        
    async def outlet(self, body: dict, user: Optional[dict] = None) -&gt; dict:
    	This filter is applied to the form data AFTER it is sent to the LLM API.
        print(f&quot;outlet:{__name__}&quot;)</code></pre><p>You intercept the messages using the <code>body</code> that is passed in. It contains all the  information e.g. <code>messages</code> which contains the message history. You can use utility methods such as <code>get_last_user_message</code>, <code>get_last_assistant_message</code> to get latest messages, do something with them and update the corresponding message <code>content</code> and return back the whole body with the updated messages e.g.</p><pre><code class="language-Python">....
from utils.pipelines.main import get_last_user_message, get_last_assistant_message

class Pipeline:
	...
    
    async def inlet(self, body: dict, user: Optional[dict] = None) -&gt; dict:
        messages = body.get(&quot;messages&quot;, [])
        user_message = get_last_user_message(messages)
        
        if user_message is not None:
            # Do something

            for message in reversed(messages):
                if message[&quot;role&quot;] == &quot;user&quot;:
                    message[&quot;content&quot;] = &quot;UPDATED CORRESPONDING CONTENT THAT LLM WILL USE&quot;
                    break

        body = {**body, &quot;messages&quot;: messages}
        return body
        
    async def outlet(self, body: dict, user: Optional[dict] = None) -&gt; dict:
    	messages = body[&quot;messages&quot;]
        assistant_message = get_last_assistant_message(messages)

	if assistant_message is not None:
            # Do something

            for message in reversed(messages):
                if message[&quot;role&quot;] == &quot;assistant&quot;:
                message[&quot;content&quot;] = &quot;UPDATED CORRESPONDING CONTENT THAT USER WILL SEE&quot;
                break

        body = {**body, &quot;messages&quot;: messages}
        return body</code></pre><p>Images are also passed in as part of message so check for <code>&quot;images&quot; in message</code> to get the Base64 encoded images and you can then use that to do any kind of image processing you want.</p><h3 id="tools">Tools</h3><p>Tools are special type of filters where a particular tool is selected based on its description and what the user has asked for e.g. if user asks math question and you want it to actually calculate instead of just hallucinating the answer then Calculator Tool will be the way to go. To create <strong>Tools </strong>your Pipeline needs to inherit from <code>FunctionCallingBlueprint</code> as it <a href="https://github.com/open-webui/pipelines/blob/cd6c092a531f886810f2789066a628907e1d2478/blueprints/function_calling_blueprint.py?ref=zohaib.me#L76C1-L96C10">implements</a> the <code>inlet</code> part of filter to do function calling.</p><pre><code>import os
import requests
from typing import Literal, List, Optional
from datetime import datetime


from blueprints.function_calling_blueprint import Pipeline as FunctionCallingBlueprint


class Pipeline(FunctionCallingBlueprint):
    class Valves(FunctionCallingBlueprint.Valves):
        # Add your custom parameters/configuration here e.g. API_KEY that you want user to configure etc.
        pass

    class Tools:
        def __init__(self, pipeline) -&gt; None:
            self.pipeline = pipeline

        def calculator(self, equation: str) -&gt; str:
            &quot;&quot;&quot;
            Calculate the result of an equation.

            :param equation: The equation to calculate.
            &quot;&quot;&quot;

            try:
                result = eval(equation)
                return f&quot;{equation} = {result}&quot;
            except Exception as e:
                print(e)
                return &quot;Invalid equation&quot;

    def __init__(self):
        super().__init__()
        self.name = &quot;My Calculator Tool Pipeline&quot;
        self.valves = self.Valves(
            **{
                **self.valves.model_dump(),
                &quot;pipelines&quot;: [&quot;*&quot;],  # Connect to all pipelines
            },
        )
        self.tools = self.Tools(self)</code></pre><p>In above example if you don&apos;t want a function in Tools class to be used then prefix it with <code>__</code> e.g. <code>__helper_function_to_do_something</code>. If you setup a Valve e.g. to get some configuration from user then you can access it in tool as <code>self.pipeline.valves.CUSTOM_PARAM</code>, though in my experience I was able to access it when function was invoked but not in <code>Tools.__init__</code> as in that case the value was still <code>None</code> for those Valves.</p><p>If you are also wondering that which model is used for figuring out the Tool, then the <code>FunctionCallingBlueprint</code> creates a Valve called <code>TASK_MODEL</code> which is used to figure out which function/tool to call. You can update it to whatever you want from the Settings in OpenWebUI if you don&apos;t prefer the default.</p><h2 id="pipe">Pipe</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2024/08/Untitled-2024-07-24-2230-8.png" class="kg-image" alt loading="lazy" width="1429" height="443" srcset="https://zohaib.me/content/images/size/w600/2024/08/Untitled-2024-07-24-2230-8.png 600w, https://zohaib.me/content/images/size/w1000/2024/08/Untitled-2024-07-24-2230-8.png 1000w, https://zohaib.me/content/images/2024/08/Untitled-2024-07-24-2230-8.png 1429w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Pipe/Manifold pipeline flow which is &quot;Chat request -&gt;Pipe -&gt; Chat response&quot;</span></figcaption></figure><p>This is when you want to take over what happens when user uses chat in OpenWebUI. The &quot;pipe&quot; pipeline allows you to integrate new LLM providers, build workflows that takes the user message and respond, complete RAG system that does retrieval and also generation using the LLM you want.</p><p>Basically if you want to take over what happens when user sends a request then you will implement the &quot;pipe&quot; function.</p><pre><code class="language-Python">class Pipeline:

    class Valves(BaseModel):
        pass

    def __init__(self):
        self.valves = self.Valves()

    async def on_startup(self):
        print(f&quot;on_startup:{__name__}&quot;)
        pass

    async def on_shutdown(self):
        print(f&quot;on_shutdown:{__name__}&quot;)
        pass

    def pipe(
        self, user_message: str, model_id: str, messages: List[dict], body: dict
    ) -&gt; Union[str, Generator, Iterator]:

        return &quot;HERE IS THE RESPONSE&quot;</code></pre><p>In the scenario I was trying to implement, I opted for this as I wanted to integrate the Stanford Storm Wiki which basically takes a topic, makes multiple calls to LLM to research the topic and create outline and finally write Wiki like article on it. </p><h2 id="manifold">Manifold</h2><p>Manifold is a special type of &quot;pipe&quot; Pipeline as it allows user to select specific model. Various LLM integration in OpenWebUI such as Anthropic, Groq uses this as they also tell OpenWebUI the list of models. So if you want to implement a new LLM provider then it likely will be a manifold </p><pre><code>class Pipeline:
	...
    def __init__(self):
    	self.type = &quot;manifold&quot;
        ...
    
    def pipelines(self) -&gt; List[dict]:
    	return [&quot;model-1&quot;, &quot;model-2&quot;]
        
    def pipe(
        self, user_message: str, model_id: str, messages: List[dict], body: dict
    ) -&gt; Union[str, Generator, Iterator]:
    	
        # Here use the `model_id` that user picked
        pass</code></pre><p></p><h1 id="how-to-add-more-pip-dependencies">How to add more PIP dependencies</h1><p>As the pipeline runs in its own docker which may not have the python package you need. In that case you can specific the requirements in the start of file using the front-matter specification. This can involve description, name, author etc. Along with that you can also specific comma separate <strong>&quot;requirements&quot;</strong> which will be used to install any new dependencies.</p><pre><code>&quot;&quot;&quot;
title: Filter Pipeline
author: open-webui
date: 2024-05-30
version: 1.1
license: MIT
description: Example of a filter pipeline that can be used to edit the form data before it is sent to LLM API.
requirements: requests
&quot;&quot;&quot;

class Pipeline:
	...</code></pre><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">The caveat here is that for this to work, you would have add the pipelines when you start the docker. If you install the pipeline through the OpenWebUI interface then it won&apos;t install these additional dependencies.```docker run -d -p 9099:9099 --add-host=host.docker.internal:host-gateway -e PIPELINES_URLS=&quot;https://github.com/open-webui/pipelines/blob/main/examples/filters/detoxify_filter_pipeline.py&quot; -v pipelines:/app/pipelines --name pipelines --restart always ghcr.io/open-webui/pipelines:main```Here the pipelines installed through PIPELINES_URLS will parse the front matter and install additional dependencies.</div></div><h1 id="other-tips">Other Tips</h1><ul><li>If you install the pipeline through OpenWebUI, it won&apos;t throw any error if it failed to add the pipeline due to dependency issue or code issue. The way I found to debug that is to get the logs from the running docker</li></ul><pre><code class="language-Bash">docker logs -f container_name</code></pre><ul><li>Look at various <a href="https://github.com/open-webui/pipelines/tree/main/examples?ref=zohaib.me">examples</a> in the pipelines repo to see whats the closest scenario to what you are trying to build. Use that as the starting point and edit those.</li></ul><p></p>]]></content:encoded></item><item><title><![CDATA[A beginners guide to fine tuning LLM using LoRA]]></title><description><![CDATA[Discover how to create a synthetic dataset, select the right metrics for evaluation, and fine-tune your model using LoRA for a narrow scenario. Plus, learn how to serve your model efficiently using LLaMa.cpp on Mac/Linux.]]></description><link>https://zohaib.me/a-beginners-guide-to-fine-tuning-llm-using-lora/</link><guid isPermaLink="false">658976e87425b441e0bdc5a6</guid><category><![CDATA[how-to]]></category><category><![CDATA[LLM]]></category><category><![CDATA[LLM-Training]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Thu, 15 Feb 2024 08:27:54 GMT</pubDate><content:encoded><![CDATA[<p></p><p>Professionally I&apos;ve been working in Outlook Copilot and building experiences to leverage the LLMs in the email flow. I&apos;ve been learning more about the technology itself and peeling the layers to get more understanding.</p><p>One aspect I was curious about how can I finetune an LLM model for a narrow scenario. The toy scenario I picked was having a model that corrects a sentence for grammar, spelling, punctuation, capitalization, etc. </p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x2139;&#xFE0F;</div><div class="kg-callout-text">Input: Leavs rustld sftly in autm brze.<br>Output: Leaves rustled softly in the autumn breeze.</div></div><p>I will go over all the steps from data generation, fine tuning and then using that model leveraging LLaMA.cpp on my Mac.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x2705;</div><div class="kg-callout-text">The fine tuned 3B parameters model beat the base model it was trained on by a big margin and even beat the 7B Mistral model.</div></div><p>Before we go in details, following are the validation results. </p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th style="text-align:left">Model</th>
<th style="text-align:right">Rouge-2</th>
<th style="text-align:right">Bleu 4-gram</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">3B Fine Tuned - Q4_K</td>
<td style="text-align:right">0.911872</td>
<td style="text-align:right">0.890461</td>
</tr>
<tr>
<td style="text-align:left">3B Fine Tuned - Q8_0</td>
<td style="text-align:right">0.904628</td>
<td style="text-align:right">0.879871</td>
</tr>
<tr>
<td style="text-align:left">Dolphin 2.0 mistral 7B - Q8_0</td>
<td style="text-align:right">0.872627</td>
<td style="text-align:right">0.804831</td>
</tr>
<tr>
<td style="text-align:left">3B Fine Tuned - Q2_K</td>
<td style="text-align:right">0.814925</td>
<td style="text-align:right">0.7469</td>
</tr>
<tr>
<td style="text-align:left">StableLM Zephyr 3b - Q8_0 (base)</td>
<td style="text-align:right">0.648531</td>
<td style="text-align:right">0.159785</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><hr><p>The various steps involved were</p><ol><li>Creating a dataset, which will be used for training and validation</li><li>Deciding the metrics used to evaluate</li><li>Creating a baseline with existing models</li><li>Fine tuning using the LoRA</li><li>Serving model using LLaMA.cpp with GGUF conversion</li></ol><h1 id="dataset-creation">Dataset creation</h1><p>Once you have the requirements of the problem you are trying to solve and also evaluating that LLMs is the right approach then to finetune you would need to create a dataset. If you already have a dataset that is clean and of high quality then awesome but I&apos;m assuming that&apos;s not the case.</p><p>In my scenario, I was able to generate synthetic dataset. The beauty of having more powerful LLMs is that you can use them to generate data to train the smaller language models. I went through the following process.</p><p>First, I created a prompt in a playground with the more powerful LLM of my choice and tried out to see if it generates both incorrect and correct sentences in the way I&apos;m expecting.</p><p>Once I had that, the next step was to make them parsable so I leveraged the ability of these powerful models to output JSON (or XML). Using this I was able to generate approx. 100 samples. This was done in a zero shot way to create my &#xA0;bootstrapping dataset which will be used to generate more similar samples. You should go over these bootstrapped samples thoroughly to check for quality of data. <br>From reading and learning about the finetuning process, quality of dataset is one of the most important aspect so don&apos;t just skimp over it.</p><p>Once I had the initial bootstrapping dataset I created a Python script to generate more of such samples using few shot prompting.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x2139;&#xFE0F;</div><div class="kg-callout-text">Zero shot prompting: The prompt doesn&apos;t contain any example of the data I&apos;m asking it to generate.<br><br>Few shot prompting: In the prompt to generate data, I also give few examples of the data so it can generate more similar samples.</div></div><p>Following is the prompt I used to generate bootstrapping dataset and then later updated it to contain examples.</p><pre><code class="language-Markdown">Generate unique sentences of varied length between small and long length. Some of them should also contain multiple. For each of those now write them in a way where a person who is not good at typing and types very quickly with partial and incorrect words will write but still being close to the intended sentences.
# Guidelines to follow:

* Create {TOTAL_LENGTH} such examples. 
* Don&apos;t prefix them with number. 
* Include examples from various domains such as science, math, literature, social media, slang etc.
* Create a diverse set of sentences, some containing all the way from only one error to all the way to errors across the sentence.
* Each of them should have numbers in it but keep the number same.
* Add various variety of errors e.g. typos, homophones, grammatical mistakes, omissions, capitalizations, and transpositions to accurately reflect real-world mistakes.

Always returns response in JSON the following format. The **array should have {TOTAL_LENGTH} items**.

```json
{
    &quot;DataArray: [
        {
            &quot;Correct&quot;: &quot;The correct string&quot;,
            &quot;FastTyped&quot;: &quot;The fast typed string&quot;
        },
        {
            &quot;Correct&quot;: &quot;The correct string&quot;,
            &quot;FastTyped&quot;: &quot;The fast typed string&quot;
        }
    ]
}
```</code></pre><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x2139;&#xFE0F;</div><div class="kg-callout-text">I observed that if I asked LLM to return a JSON like the following, it mostly failed<br><br>[<br>&#x2003;{ incorrect, correct}<br>]<br><br>But if I changed it to return the following then it complied<br><br>{<br>&#x2003;data: [{ incorrect, correct }]<br>}</div></div><p>Using this approach, I was able to create a dataset with approx. 2000+ samples. </p><p>I then further generated synthetic data to add random capitalization issues, partial sentences etc. This was done so I don&apos;t pigeon hole my data to only complete sentences with only grammatical issues i.e. adding diversity to my dataset so it can work for wide set of scenarios.</p><p>Lastly you can put all of this in Pandas Dataframe and split it into training, validation and test set and save it so you can use it in training process. <br>If you created further synthetic data, as I did with captialization and partial sentences, then make sure that each of train, validation and test set contain and consistent number of such data e.g.</p><figure class="kg-card kg-code-card"><pre><code class="language-Python"># Split the dataframe into train, test and validation sets with equal fraction of rows according to &apos;PartialSentence&apos;, &apos;LowerCase&apos; and &apos;RandomCase&apos; columns

    train_df = df.groupby(
    		list(df.columns.difference([&apos;FastTyped&apos;, &apos;Correct&apos;]))
        ).apply(lambda x: x.sample(frac=0.7, random_state=seed))</code></pre><figcaption>Code to split the data and also distributing various groups of synthetic data across the train, validation and test set.</figcaption></figure><h1 id="selecting-metrics-and-baseline">Selecting metrics and baseline</h1><p>You need to form a baseline so that you can empirically measure that if your finetuned model is actually doing better or if it became worse.</p><p>Hugging Face has a good inventory of various <a href="https://huggingface.co/metrics?ref=zohaib.me">metrics</a> along with a guide of <a href="https://huggingface.co/docs/evaluate/choosing_a_metric?ref=zohaib.me">choosing the metric</a>. I liked the Hugging Face <a href="https://github.com/huggingface/evaluate?ref=zohaib.me">evaluate library</a>, which is a good one stop shop for many of the metrics. The metrics I decided to evaluate were the following</p><ul><li><a href="https://huggingface.co/spaces/evaluate-metric/bleu?ref=zohaib.me">BLEU</a>: It attempts to evaluate the quality of the machine generated text with the ground truth (our target correct) using n-gram overlap.</li><li><a href="https://huggingface.co/spaces/evaluate-metric/rouge?ref=zohaib.me">ROUGE</a>: ROUGE-L &#xA0;attempts to measure the longest common subsequence between generated text and ground truth while ROUGE-N uses an N-gram overlap approach.</li><li><a href="https://huggingface.co/spaces/evaluate-metric/exact_match?ref=zohaib.me">Exact Match</a>: This matches if the generated text is exactly as the target text.</li></ul><p>The BLEU and ROUGE are more flexible as they are not binary score and evaluate based on quality and how it deviates from target. I added exact match at the mix to see how much it exactly gets right.</p><h1 id="creating-a-baseline-with-existing-models">Creating a baseline with existing models</h1><p>Once you figured these, the next step was to create a baseline with existing models. I choose the <a href="https://huggingface.co/TheBloke/dolphin-2.0-mistral-7B-GGUF/?ref=zohaib.me">Mistral 7B (Q8)</a>, <a href="https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF?ref=zohaib.me">Stable LM Zephyr 3b (Q8)</a>. How I ran the evaluation was that I downloaded the GGUF and ran it using LLaMA.cpp server which supports the OpenAI format. Then I used python to create my evaluation script and just point the <code>openai.OpenAI</code> API to URL that was localhost, being served by LLaMA.cpp.</p><pre><code>./server -m ~/.cache/huggingface/hub/models--TheBloke--dolphin-2.0-mistral-7B-GGUF/snapshots/3b345ee148d25b2da209c6166e855dc4845fcb4e/dolphin-2.0-mistral-7b.Q8_0.gguf -ngl 999</code></pre><p>The script skeleton looked like the following</p><pre><code class="language-Python">client = openai.OpenAI(
    base_url=&quot;http://localhost:8080/v1&quot;, # &quot;http://&lt;Your api-server IP&gt;:port&quot;
    api_key = &quot;sk-no-key-required&quot;,
)

def process_row(row, model_type):
    completion = client.chat.completions.create(
        model=&quot;gpt-3.5-turbo&quot;,
        messages=[
            {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: SYSTEM},
            {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: get_prompt(row[&apos;FastTyped&apos;], model_type)}
        ],
        temperature=0,
        seed=SEED
    )
    return completion.choices[0].message.content

    
    
 def evaluate_model(...):
 	...
    rouge = evaluate.load(&apos;rouge&apos;)
 	rouge_score = rouge.compute(predictions=predictions, references=references)
    ...</code></pre><p>Store these results for your test, validation data. </p><h1 id="fine-tuning-using-the-lora">Fine tuning using the LoRA</h1><p>Low-Rank Adaptation aka LoRA is a technique used to finetuning LLMs in a parameter efficient way. This doesn&apos;t involve finetuning whole of the base model, which can be huge and cost a lot of time and money. LoRA, instead adds a small number of trainable parameters to the model while keeping the original model parameters frozen.</p><p>You don&apos;t have to write the code frome scratch, rather there are already tools available that will help you kickstart the whole thing. The one I used was the <a href="https://github.com/Lightning-AI/lit-gpt?ref=zohaib.me">lit-gpt</a> from Lightning AI. There are other alternatives that you can also try e.g. <a href="https://github.com/OpenAccess-AI-Collective/axolotl?ref=zohaib.me">Axolotl</a>. I&apos;ll use lit-gpt for this tutorial.</p><p>Clone/Fork the lit-gpt, as you will be copying and adding some scripts to fit your need.</p><h3 id="prepare-dataset-for-using-in-finetuning">Prepare dataset for using in finetuning</h3><p>Copy scripts/prepare_alpaca.py and rename it to something relevant to your project. In that program I updated the <code>generate_prompt</code> function to use the instruction template that Zephyr3B uses. As by default the instruction template it has was Alpaca style which looks like</p><figure class="kg-card kg-code-card"><pre><code>Below is an instruction that describes a task, paired with an input that provides further context. 
Write a response that appropriately completes the request.

###Instruction:
{example[&apos;instruction&apos;]}

### Input:
{example[&apos;input&apos;]}

### Response:</code></pre><figcaption>Instruction template for Alpaca</figcaption></figure><p>While Zephyr3B has the following instruction template.</p><figure class="kg-card kg-code-card"><pre><code>&lt;|system|&gt;
{example[&apos;instruction&apos;]}&lt;|endoftext|&gt;
&lt;|user|&gt;
{example[&apos;input&apos;]}&lt;|endoftext|&gt;
&lt;|assistant|&gt;</code></pre><figcaption>Instruction template Zephyr3B</figcaption></figure><p>Its important to use the right instruction template otherwise the model may not generate responses as expected. You can generally find the instruction template supported by models in the Huggingface Model Card, at least for the well documented ones. If you are using some esoteric model which doesn&apos;t have that info, then you can see if its a finetune of a more prominent model which has those details and use that.</p><p>I also made some changes to the <code>prepare</code> function in that file to change the destination and checkpoint path, along with removing some of the things I didn&apos;t need e.g. doing a train/validation split, as I already had my own split done earlier so I just reused them.</p><p>You can see what I used here <a href="https://github.com/zabirauf/lit-gpt/blob/182935caadbe243a2412eacaf23d1f0ee37f3976/scripts/prepare_corrections_ds.py?ref=zohaib.me">scripts/prepare_corrections_ds.py</a></p><h3 id="finetuning-script">Finetuning script</h3><p>Copy the finetune/lora.py and rename it to something relevant to your project. Here I also changed the directions for checkpoints, output and where my data is. I also added a <a href="http://wandb.ai/?ref=zohaib.me">Weight &amp; Biases</a> (if you haven&apos;t used it, I would recommend checking it out) logger as that helps me keep tabs on how things are going. </p><p>The main change here to do is that in <code>validate</code> function, I picked a random sample from my validation data and use that to check the loss as the model gets trained. This way I was able to see how its progressing.</p><h3 id="start-the-finetune">Start the finetune</h3><p>Once I had all these setup, all I needed was an environment with GPUs to use for finetuneing. I opted for <a href="http://paperspace.com/?ref=zohaib.me">paperspace.com</a>. Once you have the prepared data and the scripts downloaded you can then run them as follows.</p><p>First we download the model and convert it into format that lit-gpt works with</p><pre><code class="language-bash">python scripts/download.py --repo_id stabilityai/stablelm-zephyr-3b --from_safetensors=True

python scripts/convert_hf_checkpoint.py --checkpoint_dir checkpoints/stabilityai/stablelm-zephyr-3b/</code></pre><p>Prepare the dataset using your scripts</p><pre><code>python scripts/prepare_alpaca_copy.py</code></pre><p>And finally start the finetuning. If you added wandb, make sure you have setup using CLI and added the credentials.</p><pre><code>python finetune/lore_copy.py</code></pre><p>Continue to monitor the training and once its complete then you should be good to start using it.</p><h1 id="using-your-finetuned-model">Using your finetuned model</h1><p>I like working with LLaMA.cpp as it works on multiple platforms, is pretty performant and comes with a lot of customizability. To run model on LLaMA.cpp you have to convert it to GGUF format.</p><p>The model you finetuned stored the LORA weights separately, so first you need to merge it with base model so you can have one model that contains both the base model and your finetune on top of it. lit-gpt already comes with scripts to do that.</p><pre><code>python scripts/merge_lora.py \
  --checkpoint_dir &quot;checkpoints/stabilityai/stablelm-zephyr-3b&quot; \
  --lora_path &quot;/notebooks/corrections-slm/lora/corrections/lit_model_lora_finetuned.pth&quot; \
  --out_dir &quot;/notebooks/corrections-slm/lora/corrections/merged&quot;
</code></pre><p>This will take the base model (checkpoint_dir), combine it with your finetune (lora_path) and merge it (out_dir).</p><p>Then you would need to convert this merged model to Huggingface model</p><pre><code>python scripts/convert_lit_checkpoint.py \
  --checkpoint_path &quot;/notebooks/corrections-slm/lora/corrections_run_2/merged/lit_model.pth&quot; \
  --output_path &quot;/notebooks/corrections-slm/lora/corrections_run_2/merged/model.bin&quot; \
  --config_path &quot;/notebooks/corrections-slm/lora/corrections_run_2/merged/lit_config.json&quot; </code></pre><p>Here it takes your merged model (checkpoint_path), converts to Hugging Face model (output_path) and uses the config to set certain parameters (config_path).<br><br><em>P.S: I haven&apos;t explored much on what the config currently contains and what it all means.</em></p><p>Now finally, in LLaMa.cpp there is convert-hf-to-gguf.py script that you can use to convert the previously converted Huggingface model to GGUF.</p><pre><code>pip install -r requirements-hf-to-gguf.txt

python convert-hf-to-gguf.py /notebooks/corrections-slm/lora/corrections_run_2/merged/</code></pre><p>Now you shohuld have a GGUF model that you can use LLaMa.cpp to run</p><pre><code>main --model /notebooks/corrections-slm/lora/corrections_run_2/merged/ggml-model-f16.gguf -p &quot;&lt;|system|&gt;\nFix the text.&lt;|endoftext|&gt;\n&lt;|user|&gt;whts gng on&lt;|endoftext|&gt;\n&lt;|assistant|&gt;&quot;</code></pre><hr><p><em>I am still learning how to better prepare dataset, train models and evaluate them. So please take it as a beginners path I took to do it and I&apos;m sure there are much better ways to go about this, and I&apos;ll continue my journey to learn them :).</em></p>]]></content:encoded></item><item><title><![CDATA[A guide to using Python with Supabase securely]]></title><description><![CDATA[Learn how to use Python with Supabase for web development for all your AI/LLM backend needs. Create a secure Python service that validates JWT tokens from Supabase and integrates with Next.js.]]></description><link>https://zohaib.me/using-python-edge-functions-with-supabase-securely/</link><guid isPermaLink="false">651509f9638ec807264880e4</guid><category><![CDATA[how-to]]></category><category><![CDATA[service]]></category><category><![CDATA[react]]></category><category><![CDATA[reactjs]]></category><category><![CDATA[python]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Sat, 30 Sep 2023 23:24:31 GMT</pubDate><content:encoded><![CDATA[<p>With the recent advances in Large Language Models and a lot of the innovation happening in regards to that in Python, it&apos;s becoming more advantageous to use Python not only for research purpose but also for building products that end users will use.</p><p>In the realm of JavaScript and Frontend, Supabase has been getting a great deal of traction as a Firebase alternative. It provides multiple services that you can use to build a whole SaaS product with the authentication, Postgres, Storage services it provides. It also has edge functions that leverage Deno but that means you&apos;ll be in the JavaScript ecosystem or move over to WASM.</p><p>What do you do if you need Python service as part of your stack and want an authenticated service so you can do operations at a user level?</p><p>You can deploy Python service to your favorite cloud infra (Fly.io, Digital Ocean etc.) and use JWT (JSON Web Tokens) to validate authenticated requests. This way you can continue leverage Supabase capabilities and also Python when you need it. In this tutorial we will see how to setup such a service.</p><h2 id="what-is-jwt">What is JWT</h2><p>JWT (JSON Web Token) is a compact, self-contained way to represent information securely between two parties.</p><p>A JWT consists of three parts: the Header, the Payload, and the Signature.</p><p><strong>Header</strong>: Describes the type of the token and the algorithm used for encryption.</p><ul><li>Example: <code>{&quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}</code></li></ul><p><strong>Payload</strong>: Contains the &apos;claims&apos;, which are statements about the user. This is where you can add further information you need in your service.</p><ul><li>Example: <code>{&quot;sub&quot;: &quot;123&quot;, &quot;name&quot;: &quot;John&quot;, &quot;admin&quot;: true}</code></li></ul><p><strong>Signature</strong>: Ensures that the message hasn&apos;t been changed. Computed as follows:</p><ul><li><code>HMACSHA256(base64UrlEncode(header) + &quot;.&quot; + base64UrlEncode(payload), secret)</code></li></ul><p>The same Secret that is used to create the signature is also used to validate the secret on the other end.</p><p>How it matters in our case is that Supabase generates JWT token using the Secret. We can deploy the same Secret to our Python service and then when we get the JWT token in our Python API, we can re-compute the signature and validate it&apos;s the same. If it is same then it means the token was created by trusted party.</p><p><strong>BEWARE:</strong> If your Secret gets compromised then the person/s can decode and create any JWT token to compromise your service. So handle it with same care as passwords e.g. don&apos;t make it part of code, don&apos;t store it openly etc.</p><h2 id="python-service">Python Service</h2><p>I&apos;ll be using FastAPI with Fly.io but you can use whatever cloud platform you are comfortable with to deploy a Python service.</p><p>Lets have a service that will just output the user full name if authenticated and we will go over the details of how it is implemented.</p><pre><code class="language-Python">from fastapi import FastAPI, Depends
from typing import Annotated

from models import User
from auth import  ValidUserFromJWT

app = FastAPI()

@app.get(&apos;/name&apos;)
async def name(user: Annotated[User, Depends(ValidUserFromJWT())]):

    return Response(
    	content={&quot;full_name&quot;: user.full_name},
        media_type=&quot;application/json&quot;)</code></pre><p>Here the key is <code>user: Annotated[User, Depends(ValidUserFromJWT())]</code>, as that will validate the JWT and get the user from the Database and assign it to <code>user</code>. So, by the time the function body is executed it means the request was authenticated and you can trust the request.</p><h3 id="unpacking-validuserfromjwt">Unpacking ValidUserFromJWT</h3><p></p><pre><code class="language-Python">from fastapi import Request, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

class ValidUserFromJWT:
    def __init__(self):
        pass

    async def __call__(self, request: Request):
        credentials: HTTPAuthorizationCredentials = await HTTPBearer()(request)
        if credentials:
            if not credentials.scheme == &quot;Bearer&quot;:
                raise HTTPException(status_code=403, detail=&quot;Invalid authentication scheme.&quot;)
            if not verify_jwt(credentials.credentials):
                raise HTTPException(status_code=403, detail=&quot;Invalid token or expired token.&quot;)
            user = get_user_from_JWT(credentials.credentials)
            if not user:
                raise HTTPException(status_code=403, detail=&quot;Invalid token&quot;)
            
            return user
        else:
            raise HTTPException(status_code=403, detail=&quot;Invalid authorization code.&quot;)</code></pre><p>How the token is passed in the request is using the following header</p><p><code>Authorization: Bearer TOKEN...</code></p><p>Here the <code>await HTTP Bearer(request)</code> extracts this information and then we check</p><ol><li>If the token is of type <code>Bearer</code></li><li><code>verify_jwt</code>: Verify the token is valid</li><li><code>get_user_from_JWT</code>: Get the user corresponding to the token from Database</li></ol><p>If any of those checks fail then we respond back with a HTTP Status code 4xx to let the caller know that something went wrong.</p><p>Following is what the <code>verify_jwt</code> looks like</p><pre><code class="language-Python">import time
from typing import Dict

import jwt
from decouple import config


JWT_SECRET = config(&quot;JWT_SECRET&quot;)
JWT_ALGORITHM = &quot;HS256&quot;

def verify_jwt(jwtoken: str) -&gt; bool:
    isTokenValid: bool = False

    try:
        payload = decodeJWT(jwtoken)
    except:
        payload = None
    if payload:
        isTokenValid = True
    return isTokenValid
    
 def decodeJWT(token: str) -&gt; Dict:
    try:
        decoded_token = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM], options={&quot;verify_aud&quot;: False})
        return decoded_token if decoded_token[&quot;exp&quot;] &gt;= time.time() else None
    except Exception as e:
        return {}</code></pre><p>Here we use the <code>jwt</code> python library that helps us do the validation so we don&apos;t have to build our own logic. For it to work you need to do the following</p><ol><li>Deploy <code>JWT_SECRET</code> as an environment variable to your service so you can access it from code. You can find the token from your Supabase dashboard.</li></ol><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2023/09/Getting-JWT-Secret-from-Supabase.png" class="kg-image" alt loading="lazy" width="2000" height="692" srcset="https://zohaib.me/content/images/size/w600/2023/09/Getting-JWT-Secret-from-Supabase.png 600w, https://zohaib.me/content/images/size/w1000/2023/09/Getting-JWT-Secret-from-Supabase.png 1000w, https://zohaib.me/content/images/size/w1600/2023/09/Getting-JWT-Secret-from-Supabase.png 1600w, https://zohaib.me/content/images/size/w2400/2023/09/Getting-JWT-Secret-from-Supabase.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>2. &#xA0;Ensure the Algorithm used is the same one that Supabase used. In my testing, it is by default set to &quot;HS256&quot;</p><p>Here the code validates the token and then makes sure the token isn&apos;t expired. Once we decode the token successfully, we get the payload which is described <a href="https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts?ref=zohaib.me">here</a> and it looks something like following<br></p><pre><code class="language-JSON">{
  &quot;aud&quot;: &quot;authenticated&quot;,
  &quot;exp&quot;: 1615824388,
  &quot;sub&quot;: &quot;0334744a-f2a2-4aba-8c8a-6e748f62a172&quot;,
  &quot;email&quot;: &quot;d.l.solove@gmail.com&quot;,
  &quot;app_metadata&quot;: {
    &quot;provider&quot;: &quot;email&quot;
  },
  &quot;user_metadata&quot;: null,
  &quot;role&quot;: &quot;authenticated&quot;
}
</code></pre><p>Here <code>sub</code> is the id of the user, so now you can use that to fetch the user from the database. I use Peewee as the ORM to make it easier for me to interact with database in Python.</p><pre><code class="language-Python">def get_user_from_JWT(token: str) -&gt; User | None:
    payload = decodeJWT(token)
    user_id = payload[&quot;sub&quot;]

    if user_id is not None:
        try:
        	# Get the user from database with the user id
            return User.get(User.id == user_id)
        except:
            return None
    
    return None</code></pre><p>As this tutorial is not about Peewee so I won&apos;t be diving into it but following is what the simple models look like.</p><pre><code class="language-Python">from peewee import UUIDField, TextField, ForeignKeyField, PostgresqlDatabase, Model
from uuid import UUID

db: PostgresqlDatabase = connect(DB_CONNECTION_STRING)
db.connect()

class BaseModel(Model):
    class Meta:
        database = db

# The authentication table created by Supabase
class AuthUser(BaseModel):
    id = UUIDField(primary_key=True)

    class Meta:
        table_name = &quot;auth.users&quot;

# Your User table that links to the auth table
class User(BaseModel):
    id = UUIDField(primary_key=True, constraints=[ForeignKeyField(AuthUser, field=&apos;id&apos;)])
    full_name = TextField(null=True)

    class Meta:
        table_name = &apos;users&apos;

    @staticmethod
    def get_user_with_id(id: UUID):
        return User.get(User.id == id)</code></pre><p>Now onto how to call your service from your Next.js Supabase front end.</p><p><strong>BEWARE:</strong> As the Peewee won&apos;t do any row level security here so you should make sure whenever you are fetch database rows to filter it down to ones that only user has access to.</p><h2 id="calling-your-python-service-from-nextjs">Calling your Python service from Next.js</h2><p>I&apos;m using the <a href="https://github.com/vercel/nextjs-subscription-payments?ref=zohaib.me">nextjs-subscription-payments</a> bootstrap project to setup my initial Supabase. Once you have that then you can get the user session, if they are logged in, and get the token to make a call to your service. Here is an example.</p><pre><code class="language-Typescript">import { getSession } from &apos;@/app/supabase-server&apos;;
import { redirect } from &apos;next/navigation&apos;;

export const dynamic = &apos;force-dynamic&apos;;

export default async function UserInfo() {
  const session = await getSession();

  const user = session?.user;

  if (!session) {
    return redirect(&apos;/signin&apos;);
  }

  return &lt;UserName accessToken={session.access_token} /&gt;
}</code></pre><p>Here we first get the user sessions, if there is none then it means user hasn&apos;t been signed in, so we redirect them to signin page. If the user is signed in, then the JWT token is available in <code>session.access_token</code> which we will use to make the service call.</p><p></p><pre><code class="language-Typescript">&apos;use client&apos;;

import { useEffect, useState } from &apos;react&apos;;
import { getEdgeServiceURL } from &apos;@/utils/helpers&apos;;

export default function UserName({
  accessToken
}: {
  accessToken: string;
}) {
  
  const [fullName, setFullName] = useState&lt;string&gt;(&quot;Loading...&quot;);
  
  useEffect(() =&gt; {
  	callService(accessToken).then(resp =&gt; {
    	if (resp === null) {
        	setFullName(&quot;Error calling service&quot;);
        } else {
        	setFullName(resp.full_name);
        }
    });
  }, [accessToken]);
  
  return &lt;div&gt;{fullName}&lt;/div&gt;
}

async function callService(accessToken: string) {
    const url = `${getEdgeServiceURL()}/name`;
    const headers = {
      Authorization: `Bearer ${accessToken}`
    };

    // Fetch with Bearer token in headers
    const response = await fetch(url, {
      method: &apos;GET&apos;,
      headers: headers
    });

    // Check for successful response
    if (!response.ok) {
      return null;
    }

    // Return JSON
    return await response.json();
}
</code></pre><p>This is a client side component so when it&apos;s rendered we make a service call, passing the access token in headers and then once we get a successful response we get the <code>full_name</code> from the returned JSON.</p><h2 id="conclusion">Conclusion</h2><p>This is how you can setup a Python service which validates the token so you can do user level operations securely and build your AI/LLM services that you can use in your Next.js front end.</p>]]></content:encoded></item><item><title><![CDATA[Suggestions for onboarding remote Software Engineers]]></title><description><![CDATA[Onboarding remote Software Engineers can be challenging. Here are some suggestions from my experience of onboarding multiple engineers remotely to make them become part of team.]]></description><link>https://zohaib.me/suggestions-for-onboarding-remote-engineers/</link><guid isPermaLink="false">61e0fc1220348b071569e882</guid><category><![CDATA[management]]></category><category><![CDATA[team]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Fri, 28 Jan 2022 17:00:00 GMT</pubDate><media:content url="https://zohaib.me/content/images/2022/01/F6F3F87F-438B-43EE-91CB-C1C61D22F9F8.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2022/01/F6F3F87F-438B-43EE-91CB-C1C61D22F9F8.jpeg" alt="Suggestions for onboarding remote Software Engineers"><p><br>In this post I&apos;ll go over on approach for onboarding new early hires remotely that I&apos;ve formed from my experience. Hope it helps you and your team in the process. </p><p>I transitioned to Engineering Management role, around 2 and half years ago. During that time most of it was spent managing the team remotely but I don&apos;t claim to be an expert at it but rather student as I go along and learn. </p><p>Before last year, most of the team I managed were based in same city though working from home due to COVID. These were the people I&apos;ve met and worked with in person. My team recently hired <strong>multiple engineers</strong> (early in SWE career), some in different state in <strong>USA </strong>and others in <strong>LATAM</strong>. </p><p>It was an interesting challenge onboarding them, so they become productive, make them feel part of the team, get them energized around the mission and work they are doing. I&apos;m glad to see them now contributing to our product and team. Few things you should know about it<br></p><ol><li>It will take time commitment both you as their manager, onboarding buddy/mentor and other members of the team.</li><li>There are managerial challenges you will have to understand due to different countries and laws (equipment, benefits, holidays etc.).</li><li>Building connection with the team members and between them and team happens gradually but only with conscious effort.</li></ol><p>Here are learnings from my experience and suggestions on what I currently think provides a good onboarding model<br></p><h1 id="homework-before-start-date"><strong><strong>Homework before start date</strong></strong></h1><p><br>You goal should be that in the first week have the new team member setup their machines and complete their first check-in. For this you have to do some homework before their first day<br></p><ol><li>Make sure all the steps to setup their machine for development, cloning repo etc. are written down (more on writing later).</li><li>You need to pick an onboarding buddy/mentor but it shouldn&apos;t be random member from team. Think about what area you want the new engineer to continue working in about 3-6 months. <strong>Pick the onboarding buddy</strong> who owns that area. That will help build a relationship between them from the get-go and they will be more open to discuss issues when they work on complex projects.</li><li><strong>Allocate time in onboarding buddy/mentor sprint</strong> for this. Make mentoring an explicit goal for them, this will ensure they don&apos;t take it as an extra ask alongside their regular sprint.</li><li>Ask the onboarding buddy to find couple of easy bugs that would be suitable for someone new to tech stack and code.</li><li>Create a PowerPoint/Email and ask your team members to share some details about themselves. For example, what they work on, preferred communication mode, fun fact about themselves, picture etc. This will be a great way for the new person to<strong> know the team</strong>, understand their preferred times and communication mechanism (sync/async). Send it on their starting day as a first thing.</li><li>Before they join, send them a welcoming email and do include your contact so they can reach to you if Teams/Zoom doesn&apos;t work.</li></ol><h1 id="on-their-first-week"><strong><strong>On their first week</strong></strong><br></h1><ol><li>Have a 1-1 with them on the start of their first day. You can do introductions, share what team does, how they should get started etc. Basically sharing all the homework you did to set them up for a good first week.</li><li>Schedule your <strong>regular 1-1</strong>, at minimum once per week. If you generally do bi-weekly cadence, still have once per week for first few weeks and then later you can transition to bi-weekly cadence. It&apos;s essential they get face time with you to discuss their issues, understand more about the work, team culture and company.</li><li>In the first week, would highly recommend doing <strong>quick check-in</strong>, at least once in 2 days. Have their onboarding buddy setup a daily (preferable) 1-1 with them. People can be hesitant to reach out for help so having face time with their onboarding buddy will ensure they are not stuck.</li></ol><h1 id="growing-responsibilities-and-support"><strong>Growing <strong>responsibilities and support</strong></strong></h1><p><br>I hope with above instructions they were able to get in few <strong>quick wins</strong> (check-ins) and feel confident navigating parts of the codebase, tools and processes. </p><p>Initially you would give them bug level items and smaller work so they build confidence in the first month. After that <strong>start forming a plan</strong> to have them work on a certain feature or larger change in the area of work you determined earlier. </p><p>By this time they will be comfortable working with their mentor who is also the owner of the area where they&apos;ll be working. Make sure that you continue to sync with them in 1-1, understanding how they are progressing. </p><p>In the beginning there can be <strong>unnecessary stress</strong> exacerbated by being remote. They may think they are underperforming as they don&apos;t have anything to gauge it. Understanding how they perceive about their own progress; you can help calm their nerves. If they are doing well, <strong>appreciate </strong>and communicate that. If they are struggling reach out to understand how you and team can <strong>support </strong>them.<br></p><h1 id="culture-of-writing"><strong><strong>Culture of writing</strong></strong></h1><p><br>Our team previously had some knowledge written down but most of it was tribal knowledge passed during meetings, conversations etc. In a world where the whole team is in same space, this is not optimal but the fact that people are accessible puts a band aid on this problem. </p><p>In all remote or hybrid situation that band aid is ripped out so now you have to fix the problem. The solution is simple, <strong>write things down</strong>. Execution takes repeated effort and reminder. Following are some of the things that you should have written down. I don&apos;t claim my team or I are 100% done with it but we are improving and that&apos;s the goal.<br></p><ol><li>How to setup machine</li><li>Tech stack used and primer on it</li><li>Architecture of the system</li><li>Good patterns to use</li><li>Various tools at developers disposal and how to use them</li><li>Culture (writing, collaboration, experimenting, accountability etc.)</li><li>Team meetings and processes (what various team meetings are, expectation from attendees etc.)</li><li>Dev specs (one of the real benefits of it is when there are many approaches why a particular decision was made)</li></ol><p><br>You as a manager should be promoting things to be written down. Whenever you hear something that would be beneficial for others, ask the person to write it down in team wiki.<br></p><h1 id="culture-of-sharing"><strong><strong>Culture of sharing</strong></strong></h1><p><br>As we work remotely, sharing can become harder as there is no hallway conversation to ask people what they are working on, no in person gatherings to demo stuff. </p><p>You need to have a way for people to share technical learnings and sharing about the work they do. This could be newsletter, Teams/Slack channel or a weekly meeting but there should be a <strong>platform where it&apos;s expected to share and celebrate</strong> the work being done. This platform shouldn&apos;t be restricted to your immediate team but a broader organization (not more than approx. 50 people).<br></p><ul><li>This helps in recognizing the work the new team members are doing.</li><li>See what others are working on. This helps make connections which will be beneficial when working on more broader projects.</li><li>It creates energy by seeing how the team is moving towards a goal and how their contributions are part of the bigger whole.</li><li>Sharing technical knowledge e.g. things that didn&apos;t go well, new techniques learned etc.</li></ul>]]></content:encoded></item><item><title><![CDATA[Why I created a vaccine finder]]></title><description><![CDATA[<p>I recently built <a href="https://findvaccinefor.me/?ref=zohaib.me">findvaccinefor.me</a> which gets data from WA state sources such as <a href="http://vaccinelocator.doh.wa.gov/?ref=zohaib.me">vaccinelocator.doh.wa.gov</a> and <a href="http://prepmod.doh.wa.gov/?ref=zohaib.me">prepmod.doh.wa.gov</a> and presents it in a more convenient way (IMO). I built this over few weekends as a tool for me to see where I can get vaccine</p>]]></description><link>https://zohaib.me/why-i-created-a-vaccine-finder/</link><guid isPermaLink="false">6080f41813174054500be885</guid><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Thu, 22 Apr 2021 04:59:38 GMT</pubDate><media:content url="https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.22.11.png" medium="image"/><content:encoded><![CDATA[<img src="https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.22.11.png" alt="Why I created a vaccine finder"><p>I recently built <a href="https://findvaccinefor.me/?ref=zohaib.me">findvaccinefor.me</a> which gets data from WA state sources such as <a href="http://vaccinelocator.doh.wa.gov/?ref=zohaib.me">vaccinelocator.doh.wa.gov</a> and <a href="http://prepmod.doh.wa.gov/?ref=zohaib.me">prepmod.doh.wa.gov</a> and presents it in a more convenient way (IMO). I built this over few weekends as a tool for me to see where I can get vaccine shots and in the process maybe help few other folks like me. It certainly doesn&apos;t solve all the problems.</p><p>Professionally I work on a full stack product and overtime have gotten some sense of what a good or bad user experience feels like (at least I would like to think that). The thing I generally try to focus on is <strong>friction in user flows</strong>. Each time we make something slightly harder the funnel of users completing the flow becomes narrower. </p><p>I don&apos;t have any data but my intuition is that existing vaccine scheduling systems introduce so much problems that it might result in actual harm by people delaying vaccination.</p><p>Lets go over some of the gripes I have with existing tools and why user experience is very essential even for a mundane thing as scheduling an appointment.</p><h2 id="problem-1-finding-clinic">Problem 1: Finding clinic</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.18.48.png" class="kg-image" alt="Why I created a vaccine finder" loading="lazy" width="1276" height="686" srcset="https://zohaib.me/content/images/size/w600/2021/04/Screen-Shot-2021-04-21-at-21.18.48.png 600w, https://zohaib.me/content/images/size/w1000/2021/04/Screen-Shot-2021-04-21-at-21.18.48.png 1000w, https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.18.48.png 1276w" sizes="(min-width: 720px) 720px"><figcaption>Finding vaccine with zip code</figcaption></figure><p>In the age where everyone uses some form of maps e.g. Google maps, Apple maps it&apos;s hard to parse through information based on zipcode and how many miles away it is. I believe majority of us are habituated to use maps for searching for a location and understanding it&apos;s closeness and convenience of getting there.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.24.22.png" class="kg-image" alt="Why I created a vaccine finder" loading="lazy" width="1650" height="1142" srcset="https://zohaib.me/content/images/size/w600/2021/04/Screen-Shot-2021-04-21-at-21.24.22.png 600w, https://zohaib.me/content/images/size/w1000/2021/04/Screen-Shot-2021-04-21-at-21.24.22.png 1000w, https://zohaib.me/content/images/size/w1600/2021/04/Screen-Shot-2021-04-21-at-21.24.22.png 1600w, https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.24.22.png 1650w" sizes="(min-width: 720px) 720px"><figcaption>Vaccine site showing dates in past</figcaption></figure><p>Why is it showing me dates all the way back to 2016. It should only show the dates in future, I thought that would be obvious. But it should also filter out dates for which there are no appointments available. As a user looking for vaccine, I&apos;m only interested in dates which have an appointment available so I can pick the most convenient one out of them. Currently user has to try out multiple dates and hope there is an appointment available or browse through bunch of listing to find the right date where vaccine is also available.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.28.54.png" class="kg-image" alt="Why I created a vaccine finder" loading="lazy" width="2000" height="758" srcset="https://zohaib.me/content/images/size/w600/2021/04/Screen-Shot-2021-04-21-at-21.28.54.png 600w, https://zohaib.me/content/images/size/w1000/2021/04/Screen-Shot-2021-04-21-at-21.28.54.png 1000w, https://zohaib.me/content/images/size/w1600/2021/04/Screen-Shot-2021-04-21-at-21.28.54.png 1600w, https://zohaib.me/content/images/size/w2400/2021/04/Screen-Shot-2021-04-21-at-21.28.54.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Showing vaccine clinic without any availabilities</figcaption></figure><p>Hmmm, why show me a place where there is no appointment available. What are they expecting me to do with that info? This just adds to the friction as I parse the list. Though I&apos;m glad <a href="http://prepmod.doh.wa.gov/?ref=zohaib.me">prepmod.doh.wa.gov</a> shows the map but its not much useful when I can&apos;t see where the location is in relation to me.</p><h2 id="problem-2-different-scheduling-websites">Problem 2: Different scheduling websites</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2021/04/scheduling-systems.png" class="kg-image" alt="Why I created a vaccine finder" loading="lazy" width="2000" height="1500" srcset="https://zohaib.me/content/images/size/w600/2021/04/scheduling-systems.png 600w, https://zohaib.me/content/images/size/w1000/2021/04/scheduling-systems.png 1000w, https://zohaib.me/content/images/size/w1600/2021/04/scheduling-systems.png 1600w, https://zohaib.me/content/images/size/w2400/2021/04/scheduling-systems.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Different scheduling websites</figcaption></figure><p>Can you spot the problem in above picture? Yep, you guessed right. Each clinic has a different scheduling system, some even points to a Facebook page. As a user everytime you click for scheduling, you are in for a surprise and have to go through the pain of understanding each system.</p><p>I believe <a href="prepmod.doh.wa.gov">prepmod.doh.wa.gov</a> tries to solve that problem but it only has few bigger vaccination sties and most of the places around you would likely have their own system. These systems make you fill a form before you can even see the vaccine is available at a time that is convenient for you. This results in a lot of time spent in filling forms unnecessarily to realize they don&apos;t have it.</p><p>Instead, please show me what&apos;s available and when, only when I select they can go through the process of filling all the forms they want.</p><p>I understand why we currently have such a disconnected system, likely because each provider wants to do their own thing. I think a world wide pandemic requires a response which is more national or at least at a state level even for digital experiences, otherwise I think we run a risk of a slower vaccination pace and likely more economic hit due to it.</p><h2 id="kudos">Kudos</h2><p>I certianly loved how the <a href="https://vaccinelocator.doh.wa.gov/?ref=zohaib.me">vaccinelocator.doh.wa.gov</a> provided a lot of accessibility features.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.42.32.png" class="kg-image" alt="Why I created a vaccine finder" loading="lazy" width="1026" height="1496" srcset="https://zohaib.me/content/images/size/w600/2021/04/Screen-Shot-2021-04-21-at-21.42.32.png 600w, https://zohaib.me/content/images/size/w1000/2021/04/Screen-Shot-2021-04-21-at-21.42.32.png 1000w, https://zohaib.me/content/images/2021/04/Screen-Shot-2021-04-21-at-21.42.32.png 1026w" sizes="(min-width: 720px) 720px"><figcaption>Accessibility menu</figcaption></figure><p>Its also nice to see that all these websites are dynamic and work well on mobile phone.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://zohaib.me/content/images/2021/04/scheduling-systems-2.png" class="kg-image" alt="Why I created a vaccine finder" loading="lazy" width="2000" height="1500" srcset="https://zohaib.me/content/images/size/w600/2021/04/scheduling-systems-2.png 600w, https://zohaib.me/content/images/size/w1000/2021/04/scheduling-systems-2.png 1000w, https://zohaib.me/content/images/size/w1600/2021/04/scheduling-systems-2.png 1600w, https://zohaib.me/content/images/size/w2400/2021/04/scheduling-systems-2.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Mobile friendly websites</figcaption></figure><p>This is not to dis the people helping build it as I tend to believe they probably did what they could with the resources they had. But it&apos;s just a criticism of the general laissez faire approach to building digital experience for the goverment and not investing much time and money on it. </p>]]></content:encoded></item><item><title><![CDATA[Debugging in Google Colab notebook]]></title><description><![CDATA[Introduction on debugging in Python Jupyter notebooks with examples in Google Colab using ipdb.]]></description><link>https://zohaib.me/debugging-in-google-collab-notebook/</link><guid isPermaLink="false">5ffa1b0796d94b0e631c2541</guid><category><![CDATA[python]]></category><category><![CDATA[jupyter]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Tue, 19 Jan 2021 02:23:12 GMT</pubDate><media:content url="https://zohaib.me/content/images/2021/01/Screen-Shot-2021-01-18-at-18.02.51.png" medium="image"/><content:encoded><![CDATA[<h1 id="why-use-a-debugger">Why use a debugger</h1><img src="https://zohaib.me/content/images/2021/01/Screen-Shot-2021-01-18-at-18.02.51.png" alt="Debugging in Google Colab notebook"><p>Jupyter notebooks in general have been really great for literate programming and exploratory learning. Google Colab allows you to directly create a Notebook and run algorithms on GPU (if you are doing Machine Learning) instead of having to deal with Jupyter or driver installations etc.</p><p>While I start to use Notebooks, one thing I&apos;ve missed from the regular dev environment in an IDE is a debugger. Yes, you can always use <code>print()</code> as a quick substitute for debugger but sometimes you need to explore the problem more in the state it occurred, learn how the program is being executed etc. In those cases debuggers are super handy.</p><p>Gladly there is a way to debug code within Notebook albeit not as convenient as IDE but still good enough. Python already has <code>pdb</code> which is a command line based debugger but for Jupyter there is <code><a href="https://github.com/gotcha/ipdb?ref=zohaib.me">ipdb</a></code> which is similar to pdb but for Jupyter notebooks. This debugger doesn&apos;t have any visual aspect to it as IDE do, but they allow you to perform similar capabilites such as &quot;Continue&quot;, &quot;Step into functions&quot;, evaluate expressions, jump to line etc. </p><p>Lets get into how to use <code>ipdb</code> in Google Colab.</p><h1 id="ipdb-in-google-colab">ipdb in Google Colab</h1><p>As <code>ipdb</code> is not packaged with Python itself so you need to install the package and import it.</p><pre><code class="language-Python">!pip install -Uqq ipdb
import ipdb</code></pre><h2 id="debug-when-hitting-exception">Debug when hitting Exception</h2><p>You can use the follow magic command in your Notebook to turn on debugging, so that whenever your code hits into an Exception, the code will pause and the debugger view will be opened for you to explore what caused the exception</p><figure class="kg-card kg-code-card"><pre><code>%pdb on</code></pre><figcaption>Turning on debbuging on Exception</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2021/01/ipdb-gc-debugger-on-exception-.gif" class="kg-image" alt="Debugging in Google Colab notebook" loading="lazy" width="873" height="478"></figure><p>In this example, we raised an exception and as you see that the debugger automatically starts when you hit an exception and I was able to inspect the value of <code>i</code> in that state. This is helpful when you don&apos;t know why an exception is occuring so you can pause at that point, inspect values and see what went wrong.</p><p>If you want to turn off this then just use <code>%pdb off</code>.</p><h2 id="setting-breakpoints">Setting breakpoints</h2><p>If you have a complicated piece of code which isn&apos;t working as expected for certain inputs, you can add <strong>breakpoints</strong> in the code so when you execute the code, it will pause at that breakpoint. &#xA0;You can inspect what&apos;s going on or even run the code step by step to see what each line of code is doing.</p><p>Adding a breakpoint is as simple as adding <code>ipdb.set_trace()</code>. It will cause the code to pause when it reaches this line and open the debugger view. This function also takes in parameter such as <code>ipdb.set_trace(context=6)</code>, here <code>context</code> refers to how many lines of code should it print when it pauses so you can get the code context surrounding the line it paused at.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2021/01/ipdb-gc-debugger-on-breakpoint-.gif" class="kg-image" alt="Debugging in Google Colab notebook" loading="lazy" width="873" height="407"></figure><p>Here you can see the code pauses execution when it reaches the line and I can inspect the variables such as <code>arr</code>. In the next section we will see what you can do when you are in debugger view.</p><h2 id="commands-for-ipdb">Commands for ipdb</h2><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2021/01/Screen-Shot-2021-01-18-at-17.34.16.png" class="kg-image" alt="Debugging in Google Colab notebook" loading="lazy" width="1300" height="492" srcset="https://zohaib.me/content/images/size/w600/2021/01/Screen-Shot-2021-01-18-at-17.34.16.png 600w, https://zohaib.me/content/images/size/w1000/2021/01/Screen-Shot-2021-01-18-at-17.34.16.png 1000w, https://zohaib.me/content/images/2021/01/Screen-Shot-2021-01-18-at-17.34.16.png 1300w" sizes="(min-width: 720px) 720px"></figure><p>When the debugger is started you will see a view like above. It show the code and has an arrow <code>----&gt;</code> pointed at the line where its paused at. The input field below is where you can run various commands or use it as a regular REPL to execute python code such as inspecting variable values, evaluating an expresion, setting variables etc.</p><p>The input field also allows you to enter the commands for the debugger that allows you to control the flow of execution such as stepping into a function, jumping to a certain line of code, continue till you hit another breakpoint etc. Following is the basic set of commands that I found useful. You can find a more exhaustive list on <a href="https://docs.python.org/3/library/pdb.html?ref=zohaib.me#debugger-commands">Python Docs for pdb</a>.</p><!--kg-card-begin: html--><table>
    <thead>
    <tr>
        <th>Command</th>
        <th>Description</th>
    </tr>
    </thead>
    
    <tbody>
    <tr>
        <td><b>h</b>(elp)</td>
        <td>Show various commands supported by ipdb</td>
    </tr>
    <tr>
        <td><b>h</b>(elp) COMMAND</td>
        <td>Show description of the COMMAND specificed</td>
    </tr>
    <tr>
        <td><b>c</b>(ontinue)</td>
        <td>Continue executing till it hits another breakpoint</td>
    </tr>
    <tr>
        <td><b>n</b>(ext)</td>
        <td>Execute till next line in the same code frame.<br> So if there is a function it wouldn&apos;t step into that<br> function but execute it.</td>
    </tr>
    <tr>
        <td><b>s</b>(tep)</td>
        <td>Step to next code, so if its a function,<br> it will step into the function.</td>
    </tr>
    <tr>
        <td><b>r</b>(eturn)</td>
        <td>Execute code till it returns from the current<br> function or hits another breakpoint.</td>
    </tr>
    <tr>
        <td><b>l</b>(ist)</td>
        <td>Show more of the source code surrounding the  line.</td>
    </tr>
    <tr>
        <td><b>w</b>(here)</td>
        <td>Shows the stacktrace i.e. the chain of functions<br> that made it reach the current function</td>
    </tr>
    <tr>
        <td><b>a</b>(rguments)</td>
        <td>List of arguments passed and its values to the function</td>
    </tr>
    <tr>
        <td><b>q</b>(uit)</td>
        <td>Immediately stop execution and quit the debugger</td>
    </tr>
    </tbody>
</table><!--kg-card-end: html--><h1 id="resources">Resources</h1><ul><li><a href="https://github.com/gotcha/ipdb?ref=zohaib.me">Github - ipdb</a></li><li><a href="https://docs.python.org/3/library/pdb.html?ref=zohaib.me#debugger-commands">pdb Python Docs</a></li><li><a href="https://colab.research.google.com/drive/1rs2CH79vwVdK_JUpJuQaM_BMb1JL0bdg?usp=sharing&amp;ref=zohaib.me">Google Colab notebook from blog</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Fence your TypeScript, for saner project boundaries]]></title><description><![CDATA[Solve the problem of dependency creep and add boundaries to your TypeScript project to limit what is exported and imported from a package.]]></description><link>https://zohaib.me/fence-your-typescript/</link><guid isPermaLink="false">5edd30abec3d4e7cde15deea</guid><category><![CDATA[typescript]]></category><category><![CDATA[gulp]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Tue, 09 Jun 2020 16:05:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>&quot;Good fences make good neighbors.&quot; &#xA0;&#x2014; Robert Frost, <em>Mending Wall</em></blockquote><p>Working on large projects has its problems of scale. Certain languages provide solution to these problems and other rely on tools in ecosystem to solve them. This post is a solution to two such problem in TypeScript.</p><p>When you have 10s - 100s of developers working on a single TypeScript codebase, who are part of larger organization, contributing across various projects in the code, dependency creep becomes a problem as the boundaries of various projects and modules isn&apos;t clear. Even with code reviews it becomes harder to track dependencies and contracts of your module (distinguish between what is private and what is public).</p><p>We are going to <strong>solve two problems</strong> here</p><ol><li>Have explicit boundary of package i.e. what is importable from other packages and what is internal to package.</li><li>Dependency creep i.e. explicitly call out dependencies of a package so its easier to manage them</li></ol><p>In a language like C# you have DLLs and the modifier <code>internal</code> which makes it clear what are the various projects you are using and what can be used from those projects without taking dependency on anything private to that. We can have a similar boundary in TypeScript using <strong><a href="https://www.typescriptlang.org/docs/handbook/module-resolution.html?ref=zohaib.me#path-mapping">path mapping</a></strong> and a tool <a href="https://github.com/smikula/good-fences?ref=zohaib.me"><strong>good-fences</strong></a>.</p><p>Lets look a the structure of an example project in which we create a full name using <code>fullName</code> function and log it in console.</p><pre><code>packages 
|__ app 
|    |__ lib
|         |__ index.ts
|__ hello
     |__ lib
          |__ index.ts
          |__ fullName.ts</code></pre><p>Here <code>app</code> and <code>hello</code> will act like packages in the project and <code>app</code> will take a dependency on <code>hello</code> to generate a full name.</p><figure class="kg-card kg-code-card"><pre><code class="language-TypeScript">import { fullName } from &apos;hello&apos;;

function app() {
    console.log(fullName(&apos;Zohaib&apos;, &apos;Rauf&apos;)); 
}

app();</code></pre><figcaption><strong>/packages/app/lib/index.ts</strong>: Here app uses the function <code>fullName</code> from package <code>hello</code></figcaption></figure><p><code>hello</code> is not a NPM package but instead we use the TypeScript path mapping capability which allow us to refer to a certain directory/path using an alias.</p><figure class="kg-card kg-code-card"><pre><code class="language-JSON">{
    ...
    &quot;baseUrl&quot;: &quot;./packages&quot;,
    &quot;paths&quot;: {
        &quot;hello&quot;: [
            &quot;hello/*&quot;,
            &quot;hello/lib/*&quot;,
            &quot;hello/lib/index.ts&quot;
        ]
    },
    ...
}</code></pre><figcaption><strong>tsconfig.json</strong>: Path mapping for <code>hello</code></figcaption></figure><p>The aliases can be turned into relative paths using Webpack.</p><h2 id="boundary-of-package">Boundary of package</h2><p>Now we have the package defined and use it. We expect that whoever will use the package will always refer to it using <code>hello</code> so it refers to <em>hello/lib/index.ts </em>and never refer to any of internal files e.g. <em>hello/lib/fullName.ts.</em> We rely on everyone doing the right thing which in practice is hard when you have 10s - 100s developer working. </p><p>Nothing is stopping them using it in these ways hence taking a hard dependency on the location of an internal function</p><ul><li>hello/lib/fullName</li><li>../../hello/lib/fullName</li></ul><p>Now lets use good-fences to solve this problem. We need to create a file <code>fence.json</code> in a folder which we want to fencify.</p><figure class="kg-card kg-code-card"><pre><code class="language-JSON">{
    &quot;tags&quot;: [&quot;hello&quot;],
    &quot;exports&quot;: [&quot;./lib/index&quot;],
    &quot;imports&quot;: [],
    &quot;dependencies&quot;: []
}</code></pre><figcaption>hello/fence.json</figcaption></figure><p>Here <code>tags</code> refer to the tag associated with this project so that other <code>fence.json</code> can refer to this package. We explicitly call out what is exported from this package and what are its dependencies. Here <code>imports</code> refers to other packages within the project (their fence.json tag) which it uses and <code>dependencies</code> are the NPM packages this package uses.</p><p>We&apos;ll use <code>gulp</code> to run good-fences before I compile so the build breaks at compile time</p><figure class="kg-card kg-code-card"><pre><code class="language-Javascript">const goodFences = require(&apos;good-fences&apos;);

gulp.task(&apos;check-fences&apos;, function() { 
	return new Promise((resolve, reject) =&gt; { 
    	const result = goodFences.run({rootDir: &apos;./&apos;});
        if (result.errors &amp;&amp; result.errors.length &gt; 0) {
            const message = result.errors
                .map(err =&gt; err.detailedMessage)
                .join(&apos;\n&apos;); 
            reject(new Error(message)); 
        } else { 
            resolve(); 
        }
    }); 
});</code></pre><figcaption>gulpfile.js</figcaption></figure><p>We use <code>run()</code> method which goes over all the fence files and ensure the boundaries are respected.</p><p>Lets see what happens when we use <code>import { fullName } from &apos;../../hello/lib/fullName&apos;</code></p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2020/06/good-fences-fail-to-import-2.gif" class="kg-image" alt loading="lazy"></figure><h2 id="dependency-creep">Dependency creep</h2><p>We have figured out what gets exported which solves the problem of what is internal to package and what is external i.e. importable by others. Now lets look into how we can solve the dependency creep by having fences for imports.</p><figure class="kg-card kg-code-card"><pre><code class="language-JSON">{
    &quot;tags&quot;: [&quot;app&quot;],
    &quot;exports&quot;: [&quot;lib/index.ts&quot;],
    &quot;imports&quot;: [],
    &quot;dependencies&quot;: []
}</code></pre><figcaption>app/fence.json</figcaption></figure><p>Here we explicitly call out that there is no dependency or import, in <code>app</code> package and lets see what good-fences throws. Recall we use <code>hello</code> in the package.</p><figure class="kg-card kg-image-card"><img src="https://zohaib.me/content/images/2020/06/good-fences-fail-to-import2-1.gif" class="kg-image" alt loading="lazy"></figure><p>This can be solved by adding <code>&quot;imports&quot;: [&quot;hello&quot;]</code> and now we have made an explicit decision to use that as dependency. During code review you can see the file changing and make sure this dependency is needed or not.</p><p>If I start using a NPM package, lets take example of using <a href="https://www.npmjs.com/package/is-odd?ref=zohaib.me">is-odd</a> (because I&apos;m lazy to write one line function :P) then your fence file will look like below</p><figure class="kg-card kg-code-card"><pre><code class="language-JSON">{
    &quot;tags&quot;: [&quot;app&quot;],
    &quot;exports&quot;: [&quot;lib/index.ts&quot;],
    &quot;imports&quot;: [&quot;hello&quot;],
    &quot;dependencies&quot;: [&quot;is-odd&quot;]
}</code></pre><figcaption><strong>app/fence.json</strong>: Adding is-odd NPM package as a dependency</figcaption></figure><h2 id="incremental-adoption">Incremental adoption</h2><p>You don&apos;t have to adopt good-fences across all your codebase to start seeing its advantages. You can start small and then add fences as you go along. You can add <code>fence.json</code> on any new package you create and in this way you can start fixing things as you go along.</p><p>I would recommend going over the <a href="https://github.com/smikula/good-fences/blob/master/README.md?ref=zohaib.me">documentation</a> of good-fences to learn more about it.</p><h2 id="resources">Resources</h2><ul><li>Example project: <a href="https://github.com/zabirauf/fence-your-typescript?ref=zohaib.me">https://github.com/zabirauf/fence-your-typescript</a></li><li><a href="https://github.com/smikula/good-fences?ref=zohaib.me">Github - good-fences</a></li><li><a href="https://www.typescriptlang.org/docs/handbook/module-resolution.html?ref=zohaib.me#path-mapping">TypeScript path mapping</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Using path aliases in React Native created with Expo and Typescript]]></title><description><![CDATA[Managing big apps have its own problems and one of them is managing imports. Typescript allows you to have path aliases which makes it easier to refer to modules. In this tutorial we will see how we can use path aliases in React Native app created using Expo and Typescript.]]></description><link>https://zohaib.me/using-path-aliases-in-react-native-typescript-created-with-expo/</link><guid isPermaLink="false">5d4b91f22d18d41877ba0152</guid><category><![CDATA[typescript]]></category><category><![CDATA[react-native]]></category><category><![CDATA[babel]]></category><category><![CDATA[development]]></category><category><![CDATA[video-mini-tutorials]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Thu, 08 Aug 2019 04:44:21 GMT</pubDate><media:content url="https://zohaib.me/content/images/2019/08/Screenshot-2019-08-07-21.41.22.png" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-embed-card"><iframe width="459" height="344" src="https://www.youtube.com/embed/iKAD4D9mpeU?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><img src="https://zohaib.me/content/images/2019/08/Screenshot-2019-08-07-21.41.22.png" alt="Using path aliases in React Native created with Expo and Typescript"><p></p><p>Managing a big app always has its own set of problems. One problem that I&apos;ve seen is managing the import paths to modules you are using. As your app becomes bigger, it makes it harder to manage these relative path. Especially if you have to move these files or folders around, refactoring can become harder.</p><p>How I like to manage a project is by creating folders with <code>index.ts</code> in it. I consider that folder to be its own package which exposes all its functionality through <code>index.ts</code>. This leads to a better interface of what a particular set of code provides and what are its internal details.</p><p>I have this very simple app that I created using the expo tool in React Native using Typescript. How the code was structured is that the main component refers to the counter component which is in this particular file in a separate folder <code>/src/app-main/lib/index.ts</code> so the import statement looks something like</p><pre><code>import { Counter } from &apos;./src/app-main/lib/index.ts&apos;;</code></pre><p>Typescript provider us a capability to add aliases for paths. If you open your <code>tsconfig.json</code> and then under compiler options you can add paths. </p><pre><code>{
    &quot;compilerOptions&quot;: {
        ...
        &quot;paths&quot;: {
            &quot;app-main&quot;: [&quot;./src/app-main/lib/index.ts&quot;],
            &quot;app-counter&quot;: [&quot;./src/app-counter/lib/index.ts&quot;]
        }
    }
}
</code></pre><p>This tells Typescript how to resolve <code>app-counter</code> to that particular file. <br>This is not the complete picture. In case of React Native created using expo, Babel is the one that&apos;s actually doing the transformation, so we need to tell it about those aliases as well. Following are the changes that you have to make to <code>babel.config.js</code></p><pre><code>module.exports = function(api) {
    api.cache(true);
    return {
        ...
        plugins: [
            [
                &apos;module-resolver&apos;,
                {
                    ...
                    alias: {
                        &apos;app-main&apos;: &apos;./src/app-main/lib/index.ts&apos;,
                        &apos;app-counter&apos;: &apos;./src/app-counter/lib/index.ts&apos;,
                    },
                },
            ],
        ],
    };
};
</code></pre><p>Now I can get rid of all that relative path and make the imports more cleaner e.g.</p><pre><code>import { Counter } from &apos;app-counter&apos;;</code></pre><p>Make sure you start expor using <code>-c</code> e.g. <code>yarn start -c</code> otherwise it might still use the cache and not able to resolve those new aliases. </p><p>This also make it super easy to refactor as you can change where the folder and files are placed and only need to update the aliases, rest should work fine.</p>]]></content:encoded></item><item><title><![CDATA[Securing MongoDB using Let's Encrypt certificate]]></title><description><![CDATA[Tutorial to setup TLS/SSL for MongoDB using Let's Encrypt with auto renewal of the certificate.]]></description><link>https://zohaib.me/securing-mongodb-using-lets-encrypt/</link><guid isPermaLink="false">5b97261c33ca9c0f6a5321fb</guid><category><![CDATA[nginx]]></category><category><![CDATA[mongodb]]></category><category><![CDATA[digital ocean]]></category><category><![CDATA[ubuntu]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Tue, 11 Sep 2018 18:28:04 GMT</pubDate><content:encoded><![CDATA[<p></p><p>I had to setup few instances of MongoDB in <a href="https://m.do.co/c/1fe978d3b5b4?ref=zohaib.me">DigitalOcean</a> but I also wanted to make them secure using TLS/SSL. One way would have been to pay for a wildcard certificate and deal with all the hassle associated with setting up TLS/SSL using that certificate.</p><p>Gladly for all of us we have <a href="https://letsencrypt.org/?ref=zohaib.me">Let&apos;s Encrypt</a>, which I believe is one of the best things that happened to make web secure. In this tutorial we are going to look into how to setup TLS/SSL for MongoDB using Let&apos;s Encrypt certificate along with auto renewing the certificate.</p><h2 id="making-mongodb-publicly-accessible">Making MongoDB publicly accessible</h2><p>I assume you already have a MongoDB instance setup. We need to make sure that you can access that instance publicly. &#xA0;If you haven&apos;t already made it accessible publicly then lets do that. </p><h3 id="secure-your-db">Secure your DB</h3><p>First thing first, making your DB public means that it will be susceptible to attacks which means you need to make sure it is secured. </p><p>Make sure you have auth enabled for your MongoDB so that only authorized users can access your instance and with limited privileges e.g. read-only, read-write etc. You can follow the steps in <a href="https://docs.mongodb.com/manual/tutorial/enable-authentication/?ref=zohaib.me#procedure">MongoDB documentation here </a>to setup authentication for your DB. Make sure to follow the principle of giving users the least privilege they need to be able to function. For example a service which needs to only read data from a particular DB should have a read-only role only for that DB. </p><p>Along with that do take a look at the <a href="https://docs.mongodb.com/manual/administration/security-checklist/?ref=zohaib.me">security checklist</a> provided by MongoDB to make sure all your bases are covered.</p><h3 id="make-it-publicly-accessible">Make it publicly accessible</h3><p>Now configure your MongoDB to bind to all IPs. If you have a configuration file then make sure it has the following settings</p><pre><code>net:
  port: 27017
  bindIp: 0.0.0.0</code></pre><p>Restart your mongo instance so that it picks up the new configuration. Though the port here is <code>27017</code> but generally it&apos;s better to change it so that someone can not easily guess what your port is. </p><p>Now you need to allow that port from your firewall. In case of Ubuntu we can use <a href="https://help.ubuntu.com/community/UFW?ref=zohaib.me">Uncomplicated Firewall</a></p><pre><code>sudo ufw allow OpenSSH
sudo ufw allow 27017
sudo ufw enable</code></pre><p>This will allow the SSH port to your VM so that you can still SSH into it after the firewall is turned on and then we enable <code>27017</code> port and at the end we enable the firewall. You can run <code>ufw status</code> to make sure that the firewall is indeed enabled.</p><p>Now you need to point some domain/subdomain to your VM public address/IP. You can use <code>A record</code> in your DNS configuration to point your domain to VM IP.</p><h2 id="setup-nginx">Setup Nginx</h2><p>To setup certificate using Let&apos;s Encrypt it needs to verify that your domain is actually owned by you. Simply put it does that by placing a file in your local machine and then trying to access it from your provided domain to see if it can access the file. To access the file it needs to be served using HTTP server and in this case we use Nginx.</p><pre><code>sudo apt-get update
sudo apt install nginx
sudo ufw allow &apos;Nginx Full&apos;</code></pre><p>The above will install nginx and also allow the ports using the firewall that we enabled. Now we need to specify the domain in the nginx configuration.</p><p>First make a backup of <code>/etc/nginx/sites-available/default</code> and then edit it. &#xA0;In configuration where it specifies <code>server_name _</code> make sure to replace it with your domain e.g. <code>server_name foo.example.com</code>. You can then run <code>sudo nginx -t</code> to confirm if the configuration file is valid.</p><p>At the end restart the nginx so it picks up the new configuration file</p><pre><code>sudo systemctl reload nginx</code></pre><h2 id="setup-certbot">Setup certbot</h2><p>Certbot is the tool that automatically sets up the TLS/SSL certificate for the HTTP server.</p><pre><code>sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install software-properties-common
sudo apt-get install python-certbot-nginx </code></pre><p>This will install the certboot tool. You can take a look at <a href="https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx?ref=zohaib.me">certbot website</a> to see the steps to install it for your specific machine.</p><h2 id="create-tls-ssl-certificate-for-mongodb">Create TLS/SSL certificate for MongoDB</h2><p>We need to run certbot to let it configure the certificate and then from that certificate we need to create the <code>.pem</code> file containing the public and private certificate chain which MongoDB uses.</p><p>Run the following and it will start a step by step wizard for setting up TLS/SSL for your domain. Go through the steps in the wizard.</p><pre><code>sudo certbot --nginx -d foo.example.com</code></pre><p>Now you should have a certificate installed and your HTTP web server will be serving pages using HTTPS. You can verify that by visiting your domain.</p><p>I assume you have a <code>mongodb</code> user and your <code>mongod</code> instance is being run using that user. Lets create the <code>mongo.pem</code> file which will then be used by MongoDB.</p><pre><code>sudo cat /etc/letsencrypt/archive/foo.example.com/{fullchain1.pem,privkey1.pem} | sudo tee /etc/ssl/mongo.pem
sudo chown mongodb:mongodb /etc/ssl/mongo.pem
sudo chmod 600 /etc/ssl/mongo.pem</code></pre><p>So here we are concatenating the content of two files <code>fullchain1.pem</code> and <code>privkey1.pem</code> into a single file <code>mongo.pem</code>.</p><p>Edit your mongo configuration file to have the following</p><pre><code>net:
  port: 27017
  bindIp: 0.0.0.0
  ssl:
    mode: requireSSL
    PEMKeyFile: /etc/ssl/mongo.pem</code></pre><p>The <code>requireSSL</code> requires all nodes in your mongo cluster to use SSL. Though that is preferred but if you don&apos;t want that then you can change it to some other value as mentioned in <a href="https://docs.mongodb.com/manual/tutorial/configure-ssl/?ref=zohaib.me">MongoDB documentation</a>.</p><p>If you restart your mongod it should now be using the TLS/SSL.</p><p>You would think you are done but we have a little more to go so bare with me. Let&apos;s Encrypt only creates a certificate for a specific period and it will be invalid after that hence we have to renew the certificate after a while. We don&apos;t want to go through the process again every month or so hence we will use a simple script to renew it for us.</p><h2 id="setup-certificate-renew-script">Setup certificate renew script</h2><p>Lets create a script <code>/path/to/renew-mongo-cert.sh</code> and make sure it has the following contents</p><!--kg-card-begin: html--><script src="https://gist.github.com/zabirauf/bda54230ca1335c1cf00e3adba682ee7.js"></script><!--kg-card-end: html--><p>Make sure to set the <code>DOMAIN</code> to your domain.</p><p>The script is basically doing what we did earlier. It runs <code>certbot renew</code> to renew the certificate which creates new files which it concatenates into <code>mongo.pem</code> and restarts the mongod.</p><p>Be aware the script will restart the MongoDB so if you don&apos;t want it to automatically restart then remove it but you would have to make sure it gets restarted so it picks up the new cert.</p><p>Make the script executable <code>chmod +x /path/to/renew-mongo-cert.sh</code>. We will leverage cron to setup a recurring task of executing the script. Run <code>crontab -e</code> and add the following</p><pre><code>0 0 1 * * /path/to/renew-mongo-cert.sh</code></pre><p>This will setup a cron job to run the script first day of the month at midnight.</p><p>Lastly run <code>sudo certbot renew --dry-run</code> to make sure that the certbot renewal works properly. This will not actually renew it but just dry run the process.</p><p>You now have a TLS/SSL setup for your MongoDB with auto renewal of certificate.</p><h3 id="extra-recommendation-for-production-dbs">Extra recommendation for production DBs</h3><p>Now you have a valid TLS/SSL certificate, but we also have a dependency on Let&apos;s Encrypt servers to renew the certificate. If you are using this in a critical production database then make sure to modify the renew script and cron job to cater for failure if the Let&apos;s Encrypt servers ever go down so that you are not stranded with an expired certificate.</p><h2 id="references">References</h2><ul><li><a href="https://letsencrypt.org/?ref=zohaib.me">Let&apos;s Encrypt</a></li><li><a href="https://docs.mongodb.com/manual/tutorial/enable-authentication/?ref=zohaib.me#procedure">Enable Auth - MongoDB Manual</a></li><li><a href="https://docs.mongodb.com/manual/administration/security-checklist/?ref=zohaib.me">Security Checklist - MongoDB Manual</a></li><li><a href="https://gist.github.com/zabirauf/bda54230ca1335c1cf00e3adba682ee7?ref=zohaib.me">Gist for certificate renewal script</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Upgradeable smart contracts in Ethereum]]></title><description><![CDATA[Ethereum contracts are immutable but there is a way to design contracts which can be upgraded, so you can fix bugs quickly and add features. We'll see how to create upgradable contracts.]]></description><link>https://zohaib.me/upgradeable-smart-contracts-in-ethereum/</link><guid isPermaLink="false">5b6a6e7de9dcd7717bc858df</guid><category><![CDATA[ethereum]]></category><category><![CDATA[solidity]]></category><category><![CDATA[blockchain]]></category><dc:creator><![CDATA[Zohaib Rauf]]></dc:creator><pubDate>Wed, 18 Apr 2018 16:11:29 GMT</pubDate><media:content url="https://zohaib.me/content/images/2018/04/IMG_0128.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://zohaib.me/content/images/2018/04/IMG_0128.jpg" alt="Upgradeable smart contracts in Ethereum"><p>Imagine a world where a software is maintaining millions of dollars worth of money, but there is an exploit which allows the hacker to take all that money away. Now imagine you can&apos;t do much about it because you can&apos;t update your software, so all you can do is wait and watch or pull the plug of the whole server.</p>
<p>This is a world we are living in with the software/contracts being developed for the Ethereum blockchain.</p>
<p>Immutability of blockchain has its advantages in making sure that things are tamper proof and the whole history of change can be seen publicly and audited. When it comes to smart contracts, immutability of the contract code has its disadvantages which makes it hard to update in case of bugs. The <a href="http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/?ref=zohaib.me">DAO</a> and the <a href="https://cointelegraph.com/news/parity-multisig-wallet-hacked-or-how-come?ref=zohaib.me">Parity Wallet</a> exploit are a good example of why smart contracts should have the capability to upgrade.</p>
<p>There is no de facto way of upgrading a contract. Several approaches have been developed and I&apos;m going to discuss one of the better ones in my opinion.</p>
<h3 id="understandinghowcontractsarecalled">Understanding how contracts are called</h3>
<p>In Ethereum virtual machine (EVM) there are various ways by which <strong>contract A</strong> can invoke some function in <strong>contract B</strong>. Lets go over them</p>
<ol>
<li>
<p><strong>call</strong>: This calls the <strong>contract B</strong> at the given address with provided data, gas and ether. When the call is made the context is changed to this <strong>contract B</strong> context i.e. the storage used will be of the called contract. The <code>msg.sender</code> will be the calling <strong>contract A</strong> and not the original <code>msg.sender</code>.</p>
</li>
<li>
<p><strong>callcode</strong>: This is similar to <strong>call</strong> but the only difference is that the context will be of original <strong>contract A</strong> so the storage will not be changed to that of the called <strong>contract B</strong>. This means the code in the <strong>contract B</strong> can essentially manipulate storage of the <strong>contract A</strong>. The <code>msg.sender</code> will be the calling <strong>contract A</strong> and not the original <code>msg.sender</code>.</p>
</li>
<li>
<p><strong>delegatecall</strong>: This is similar to <strong>callcode</strong> but here the <code>msg.sender</code> and <code>msg.value</code> will be the original ones. It&apos;s as if the user called this <strong>contract B</strong> directly.</p>
</li>
</ol>
<p>Here <strong>delegatecall</strong> seems of interest, as this can allow us to proxy a call from the user to a different contract. Now we need to build a contract that allows us to do that.</p>
<h3 id="upgradeablesmartcontract">Upgradeable smart contract</h3>
<p>We will write a smart contract that has an owner and it proxies all calls to a different contract address. The owner should be able to upgrade the contract address to which the code proxies to.</p>
<p><img src="https://zohaib.me/content/images/2018/04/IMG_0129.jpg" alt="Upgradeable smart contracts in Ethereum" loading="lazy"></p>
<p>This is what that contract would look like</p>
<script src="https://gist.github.com/zabirauf/a0d2456278bf388138f95da2804ce5f6.js"></script>
<h4 id="letsdissectthecode">Let&apos;s dissect the code</h4>
<p>Let&apos;s dissect above code. <code>function () payable public</code> is the fallback function. When a user calls a contract without a function call (when sending ether) or a function call that is not implemented, it all gets routed to the fallback function. The function can do whatever it wants to. As we don&apos;t know what functions our contracts will have, nor how will it change so this is the ideal place where we route all calls made by user to the required contract address.</p>
<p><img src="https://zohaib.me/content/images/2018/04/IMG_0130.jpg" alt="Upgradeable smart contracts in Ethereum" loading="lazy"></p>
<p>Inside this function we have some EVM assembly, as what we want to do is not currently directly available in Solidity.</p>
<pre><code>let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
</code></pre>
<p>As the user can send data which would contain information about which function to call and what are the arguments of that function so we also need to send it to our implementation contract. In Solidity the address to free memory is stored at location <code>0x40</code>. We load the data at <code>0x40</code> address and assign it name <code>ptr</code>. Then we call <code>calldatacopy</code> to copy all the data passed in by user to memory starting from <code>ptr</code> address.</p>
<pre><code>let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
</code></pre>
<p>Here we use the <code>delegatecall</code> as discussed before to call the contract. The result returned is either <code>0</code> or <code>1</code>.<br>
<code>0</code> means that there was some failure so we need to revert. <code>1</code> means the contract code execution was successful.</p>
<pre><code>let size := returndatasize
returndatacopy(ptr, 0, size)
</code></pre>
<p>As the execution of code might have returned some data so we need to return it back to user, here we copy the returned data to memory starting at <code>ptr</code> address.</p>
<pre><code>switch result
case 0 { revert(ptr, size) }
case 1 { return(ptr, size) }
</code></pre>
<p>Lastly we just decided whether to return or revert based on the result of <code>delegatecall</code>.</p>
<p>We inherit the contract from <a href="https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/ownership/Ownable.sol?ref=zohaib.me">Ownable</a> so the contract will have an owner. We expose <code>updateImplementation(address)</code> function to update the implementation and it can only be executed by the owner of contract.</p>
<h3 id="deployanupgradeablecontract">Deploy an upgradeable contract</h3>
<p>First deploy the <code>UpgradeableContractProxy</code> contract. Let&apos;s say we have a global calculator contract, which has list of numbers in storage and we can get the sum of all numbers in it.</p>
<script src="https://gist.github.com/zabirauf/7adef6627e81124a66d9b4e670218edc.js"></script>
<p>Let&apos;s take that contract and deploy it. It should give us some address, now use that address as one of the parameters to call <code>updateImplementation</code> of the previously deployed <code>UpgradeableContractProxy</code> contract. Now our proxy should point to the deployed <code>GlobalCalculator_V1</code> contract.</p>
<p>Now use the <code>GlobalCalculator_V1</code> contract interface (ABI) to call the proxy contract. So if you are using web3js then you can do the following</p>
<pre><code>const myGlobalCalculator = new web3.eth.Contract(GlobalCalculator_V1_JSON, &apos;PROXY_CONTRACT_ADDRESS&apos;);
</code></pre>
<p>Now when you make a function call to proxy, it will delegate the call to your global calculator contract. Let say you called few <code>addNum(uint)</code> to add numbers to the <code>_nums</code> list in storage and now it has <code>[2, 5]</code> as value.</p>
<p>Let&apos;s add a multiplication functionality in your updated contract. We create a new contract which inherits from <code>GlobalCalculator_V1</code> and add that feature.</p>
<script src="https://gist.github.com/zabirauf/bef09015496b5e3126c316fdb18ccdcd.js"></script>
<p>Let&apos;s deploy <code>GlobalCalculator_V2</code> contract and call <code>updateImplementation(address)</code> of the proxy to point to this newly deployed contract address.</p>
<p>You can use the <code>GlobalCalculator_V2</code> contract interface (ABI) to call same proxy contract which would route the call to new implementation.</p>
<pre><code>const myGlobalCalculator_v2 = new web3.eth.Contract(GlobalCalculator_V2_JSON, &apos;PROXY_CONTRACT_ADDRESS&apos;);
</code></pre>
<p>You should have the ability to call <code>getMul()</code> now. One thing to notice is that if I call <code>getSum</code> it will return <code>7</code> as the <code>_nums</code> in storage is still the same as the old one and have value <code>[2, 5]</code>. So in this way our storage is maintained and we can add more functionality to our contract. The users also don&apos;t have to change the address as they always call the proxy contract which delegates it to other contracts.</p>
<h4 id="pros">Pros</h4>
<ol>
<li>Ability to upgrade code.</li>
<li>Ability to retain storage, no need to move data.</li>
<li>Easy to change and fix.</li>
</ol>
<h4 id="cons">Cons</h4>
<ol>
<li>As we use fallback function to delegate so ether can&apos;t be directly sent to contract (due to not enough gas when using <code>.transfer</code> or <code>.send</code>). We need to have a specific function that needs to be called for receiving ether.</li>
<li>There is some gas cost for executing the proxy logic.</li>
<li>Your updated codes needs to be backwards compatible.</li>
<li>The update capability is controlled by one entity. This can be solved by writing a contract that uses tokens and voting mechanism to allow the community to decide whether to update or not.</li>
</ol>
<p>If you want to look at an example project using truffle which is testing an upgradeable contract <a href="https://github.com/zabirauf/payroll?ref=zohaib.me">see this</a>.</p>
<h4 id="disclaimer">Disclaimer</h4>
<p>Don&apos;t use the above contract for production as it&apos;s not audited for exploits. It is only provided here for educational purpose.</p>
<h3 id="reference">Reference</h3>
<ul>
<li><a href="https://github.com/zabirauf/payroll?ref=zohaib.me">Github example project</a></li>
<li><a href="https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/ownership/Ownable.sol?ref=zohaib.me">Zepplin Ownable contract</a></li>
<li><a href="https://github.com/zeppelinos/labs/blob/master/upgradeability_ownership/contracts/Proxy.sol?ref=zohaib.me">Zepplin OS Proxy contract</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>